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

Time:05-31

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.

>>> EDIT: SOLVED! SOLUTION POSTED BELOW. <<<

CodePudding user response:

fisrt of all what is setActiveElement={inView ? '#home' : '' } in your components?

you have to pass different refs for each component you want to track if it's in the viewport so form react-intersection-observer documents:

import * as React from "react";
// @ts-ignore Wrong type
import { createRoot } from "react-dom/client";
import { useInView } from "react-intersection-observer";
import ScrollWrapper from "./elements/ScrollWrapper";
import "./styles.css";

function App() {

  const { ref, inView } = useInView({
    threshold: 0
  });

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

  return (
    <ScrollWrapper inView={inView}>
      <div ref={ref} className="inview-block">
        <h2>
          Element is inside the viewport: <strong>{inView.toString()}</strong>
        </h2>
      </div>
      <div ref={ref2} className="inview-block">
        <h2>
          Element is inside the viewport: <strong>{inView2.toString()}</strong>
        </h2>
      </div>
    </ScrollWrapper>
  );
}

const root = createRoot(document.getElementById("root"));
root.render(<App />);


CodePudding user response:

** 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!

  • Related