Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to identify React components with the useInView hook and pass their values to a child component?

I am creating a navigation bar with React that visually highlights the active component when it is scrolled into view by the user. So, they scroll to the 'About' component and the user icon would highlight and the home icon would return to normal. It looks like this:

enter image description here

I am trying to do this in 2 steps: (1) using the useInView hook in my App.jsx component to set the 'activeElement' with a useState hook when the user scrolls to the useInView {ref}, and (2) passing the 'activeElement' as a useState variable to the child Nav component via props and useEffect to update activeNav in the Nav component when the user is scrolling.

Here is my code for the App component, which I have been testing within the paragraph tags. Currently, activeElement is not being affected by scrolling.

const App = () => {
  const { ref, inView, entry } = useInView({
    /* Optional options */
    threshold: 0,
  });

  const [activeElement, setActiveElement] = useState('#')
  
  return (
    <>
      <Header ref={ref} setActiveElement={inView ? '#home' : '' }/>
      <Nav activeElement={activeElement}/>
      <About ref={ref} setActiveElement={inView ? '#about' : '' } />
      <p>{activeElement}</p>
      <Services ref={ref} setActiveElement={inView ? '#services' : '' } />
      <Contact ref={ref} setActiveElement={inView ? '#contact' : '' }/>
      <Footer />
      <p>{activeElement}</p>
    </>
  )
}

export default App

And here is the code for my Nav component:

const Nav = ({activeElement}) => {
  const [activeNav, setActiveNav] = useState('#');
  
  useEffect(() => {
    setActiveNav(activeElement);
  })

  return (
    <nav>
      <a href="#" onClick={() => setActiveNav('#')} className={activeNav === '#' ? 'active' : ''}><AiOutlineHome /></a>
      <a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><AiOutlineUser /></a>
      <a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiOutlineBookOpen /></a>
      <a href="#services" onClick={() => setActiveNav('#services')} className={activeNav === '#services' ? 'active' : ''}><FaUncharted /></a>
      <a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><RiMessage2Line /></a>
    </nav>
  )
}

export default Nav

What is wrong with my useInView execution? And am I passing the activeElement variable to the Nav component correctly?

Thanks for taking the time to read this through.

like image 996
BettyWhite Avatar asked Dec 18 '25 15:12

BettyWhite


1 Answers

** SOLUTION: **

After some digging, and with thanks to the commenter, Ali Mirzaei, for helping to identify where the issue was occuring, we found 2 problems:

  1. I needed a separate useInView hook for each element being observed.
  2. Using 'ref' on a component call was creating an error: "Warning: Function components cannot be given refs. Attempts to access this ref will fail." So, I used the answer from https://stackoverflow.com/a/65756885/13471663 to pass the ref as a prop named innerRef

Working code is as follows:

const App = () => {
  const { ref, inView } = useInView();

  const { ref: ref1, inView: inView1 } = useInView();

  const { ref: ref2, inView: inView2 } = useInView();

  const { ref: ref3, inView: inView3 } = useInView();

  const { ref: ref4, inView: inView4 } = useInView();

  const [activeElement, setActiveElement] = useState('#')

  useEffect(() => {
    if (inView) {
      setActiveElement('#home');
      console.log('home');
    };
    if (inView1) {
      setActiveElement('#about')
      console.log('about');
    };
    if (inView2) {
      setActiveElement('#experience')
      console.log('experience');
    };
    if (inView3) {
      setActiveElement('#services')
      console.log('services');
    };
    if (inView4) {
      setActiveElement('#contact')
      console.log('contact');
    };
  }, [inView, inView1, inView2, inView3, inView4])
  
  return (
    <>
      <Header innerRef={ref} />
      <Nav activeElement={activeElement}/>
      <About innerRef={ref1} />
      <p>{activeElement} {inView.toString()}</p>
      <Experience innerRef={ref2} />
      <Services innerRef={ref3} />
      <Contact innerRef={ref4} />
      <Footer />
      <p>{activeElement}</p>
    </>
  )
}

export default App

And for the Nav component:

const Nav = ({activeElement}) => {
  const [activeNav, setActiveNav] = useState('#home');
  
  useEffect(() => {
    setActiveNav(activeElement);
  })

  return (
    <nav>
      <a href="#" onClick={() => setActiveNav('#home')} className={activeNav === '#home' ? 'active' : ''}><AiOutlineHome /></a>
      <a href="#about" onClick={() => setActiveNav('#about')} className={activeNav === '#about' ? 'active' : ''}><AiOutlineUser /></a>
      <a href="#experience" onClick={() => setActiveNav('#experience')} className={activeNav === '#experience' ? 'active' : ''}><HiOutlineBookOpen /></a>
      <a href="#services" onClick={() => setActiveNav('#services')} className={activeNav === '#services' ? 'active' : ''}><FaUncharted /></a>
      <a href="#contact" onClick={() => setActiveNav('#contact')} className={activeNav === '#contact' ? 'active' : ''}><RiMessage2Line /></a>
    </nav>
  )
}

export default Nav

And here is an example of the innerRef use from a component:

const About = ({ innerRef }) => {
  return (
    <section id='about'>
      <div ref={innerRef}>
        About
      </div>
    </section>
  )
}

Hope that helps anyone out there experiencing the same issue!

like image 127
BettyWhite Avatar answered Dec 20 '25 08:12

BettyWhite



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!