1

This is my first time dealing with Gatsby and React, so I might be using the wrong approach on this matter. Anyway, this is what is going on.

From the gatsby-starter-hello-world, I'm building this site that will be composed of a front page with a Hero on the top, holding the intro information. Right bellow, I'm intending to insert some content (I'm not sure about what yet), with this <Header /> appearing on scroll. For that part, I'm intending on use Headroom.js, which already works in the site. The thing is I need it to be triggered only after the bottom of the Hero component touches the top of the viewport. And this will happen only in desktop and laptops. On mobile I intend to make it a fixed navbar.

Anyway, right now, this is what I have for a Layout.js

import React from "react"

import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"

import Hero from "./index/hero"
import Header from "./header"
import Headroom from "react-headroom"

const Layout = ({ children }) => {
  const data = useStaticQuery(graphql`
    query SiteTitleQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)

  function() getHeroSize {
    var heroHeight = Hero.clientHeight;
  }


  return (
    <>
      <Hero siteTitle={data.site.siteMetadata.title} ref="inner" />
      <div className={"container mx-auto"}>

      <Headroom pinStart={heroHeight}>
      <Header siteTitle={data.site.siteMetadata.title} />
      </Headroom>


        <div
          //style={{
          //  margin: `0 auto`,
          //  maxWidth: 960,
          //  padding: `0px 1.0875rem 1.45rem`,
          //  paddingTop: 0,
          //}}

        >
          <main>{children}</main>
          <footer>
            © {new Date().getFullYear()}, Construído com
            {` `}
            <a href="https://www.gatsbyjs.org">Gatsby</a>
          </footer>
        </div>
      </div>

    </>
  )
}

Layout.propTypes = {
  children: PropTypes.node.isRequired,
}

export default Layout

The function getHeroSize above is more like an intention display of what I'm thinking. It doesn't really work.

I'm using Tailwind CSS and sourcing some content from Trello using gatsby-source-trello. Not yet sure how to make this Layout.js, but this is what I've got from some testing that worked pretty good, so far. I understand that there'll be some work to do within gatsby-node.js, but I believe this header will be there in any other page I create, so.

Any thoughts, suggestions or links to documentation would be really appreaciated. Thanks in advance!

EdBucker
  • 63
  • 1
  • 3

1 Answers1

0

Your Hero component would need to use forwardRef to pass the ref to the nearest React element:

const Hero = React.forwardRef((props, ref) => 
  <div ref={ref}>
    {props.title}
  </div>

Then you'll need to create a ref in Layout and pass that to Hero:

import React, { useRef } from "react"

const Layout = ({ children }) => {
  const heroRef = useRef()
  // ...
  return (
    <>
      <Hero title={data.site.siteMetadata.title} ref={heroRef} />
      {/* ... */}
    </>
  )
}

Finally, you'll want to use a hook to measure the actual height of the heroRef.current element. Here's one I use:

import { useEffect, useState } from "react"
import debounce from "lodash/debounce"

export default (ref, ttl = 100) => {
  const [dimensions, setDimensions] = useState()

  useEffect(() => {
    if (!ref.current) return

    const measure = debounce(() => {
      if (ref.current) {
        setDimensions(ref.current.getBoundingClientRect())
      }
    }, ttl)

    measure()
    window.addEventListener("resize", measure)

    return () => {
      window.removeEventListener("resize", measure)
    }
  }, [ref, ttl])

  return dimensions
}

And here's how you might add that to Layout:

import useElementDimensions from "hooks/useElementDimensions"

const Layout = ({ children }) => {
  const heroRef = useRef()
  const heroDims = useElementDimensions(heroRef) || { top: 0, height: 0 }
  const heroBottom = heroDims.top + heroDims.height

  return (
    <Headroom pinStart={heroBottom}>
      <Header siteTitle={data.site.siteMetadata.title} />
    </Headroom>
  )
}
coreyward
  • 77,547
  • 20
  • 137
  • 166
  • It makes a lot of sense. Thank you very much for your answer. However, it's returning one error: _TypeError: heroDims is undefined_. I've done as you suggested, using forwaredRef in hero.js, ending with this `const Hero = React.forwardRef(({ siteTitle }, ref ) => (
    `, then added this same hook you gave within hooks/useElementsDimensions and added the proper code into Layout.js. Gonna recheck my code now to see if i didn't mistyped something
    – EdBucker Jan 13 '20 at 21:50
  • Oh yeah, sorry it'll come back `undefined` until the ref is set. I'll typically create a fallback. I'll update the code now. – coreyward Jan 13 '20 at 21:54
  • It did work! Thanks! I see we need to set a origin point with top and height values. That´s clever! Shall I make and anwser with your question? – EdBucker Jan 13 '20 at 22:25
  • I'm sorry, I missed the check button to mark your as the right one. – EdBucker Jan 13 '20 at 22:33