Julian
Julian

Reputation: 657

React scroll nav using useRef in seperate routes

Futher to my last question here, I have been trying to map the refs to other routes. The scroll handler is working but ref.current is null. So I am looking for an answer to this dilema. Using no external dependencies, how can I fix this issue?

App.tsx

import React, { useEffect, useRef } from "react";
import { BrowserRouter, Route, NavLink, useLocation } from "react-router-dom";
import Home from "./pages/Home";
import "./styles.css";

const Header = ({ refs }) => {
  const location = useLocation();

  useEffect(() => {
    console.log("location", location.pathname);
    switch (location.pathname) {
      case "/about":
        scrollSmoothHandler(refs.aboutRef);
        break;
      case "/contact":
        scrollSmoothHandler(refs.contactRef);
        break;
      case "/hero":
        scrollSmoothHandler(refs.heroRef);
        break;

      default:
        scrollSmoothHandler(refs.homeRef);
        break;
    }
  }, [location, refs]);

  const scrollSmoothHandler = ref => {
    console.log("Triggered.");
    console.log(ref.current);
    //ref.current.scrollIntoView({ behavior: "smooth" });
  };

  return (
    <>
      <NavLink to="/hero" activeClassName="selected">
        Hero
      </NavLink>
      <NavLink to="/about" activeClassName="selected">
        About
      </NavLink>
      <NavLink to="/contact" activeClassName="selected">
        Contact
      </NavLink>
    </>
  );
};

function App() {
  const homeRef = useRef(null);
  const heroRef = useRef(null);
  const aboutRef = useRef(null);
  const contactRef = useRef(null);

  return (
    <div ref={homeRef} className="App">
      <BrowserRouter>
        <Header refs={{ aboutRef, contactRef, heroRef, homeRef }} />
        <Route
          exact
          to="/"
          refs={{ aboutRef, contactRef, heroRef, homeRef }}
          component={Home}
        />
        // More routes here.
      </BrowserRouter>
    </div>
  );
}

export default App;

Home.tsx

import React, { Fragment, forwardRef, useRef } from "react";
import "../styles.css";

const Hero = forwardRef((props, ref) => {
  return (
    <section ref={ref}>
      <h1>Hero Section</h1>
    </section>
  );
});

const About = forwardRef((props, ref) => {
  return (
    <section ref={ref}>
      <h1>About Section</h1>
    </section>
  );
});

const Contact = forwardRef((props, ref) => {
  return (
    <section ref={ref}>
      <h1>Contact Section</h1>
    </section>
  );
});

function Home(refs) {
  const heroRef = useRef(refs.heroRef);
  const aboutRef = useRef(refs.aboutRef);
  const contactRef = useRef(refs.contactRef);
  return (
    <Fragment>
      <Hero ref={heroRef} />
      <About ref={aboutRef} />
      <Contact ref={contactRef} />
    </Fragment>
  );
}

export default Home;

You can find a link to my Code Sandbox: here. Forks are much appreciated.

Upvotes: 4

Views: 898

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281864

You cannot pass refs as props to other components with the name prop without using forwardRef on the commponent. You need to assign another name to it in order for it to work, For example innerRefs.

Also to pass on refs as prop to the Route component, make use of render prop method

App.tsx

import React, { useEffect, useRef } from "react";
import { BrowserRouter, Route, NavLink, useLocation } from "react-router-dom";
import Home from "./pages/Home";
import "./styles.css";

const Header = ({ innerRefs }) => {
  const location = useLocation();

  useEffect(() => {
    console.log("location", location.pathname);
    switch (location.pathname) {
      case "/about":
        scrollSmoothHandler(innerRefs.aboutRef);
        break;
      case "/contact":
        scrollSmoothHandler(innerRefs.contactRef);
        break;
      case "/hero":
        scrollSmoothHandler(innerRefs.heroRef);
        break;

      default:
        scrollSmoothHandler(innerRefs.homeRef);
        break;
    }
  }, [location, innerRefs]);

  const scrollSmoothHandler = innerRef => {
    console.log("Triggered.");
    console.log(innerRef.current);
    innerRef.current.scrollIntoView({ behavior: "smooth" });
  };

  return (
    <>
      <NavLink to="/hero" activeClassName="selected">
        Hero
      </NavLink>
      <NavLink to="/about" activeClassName="selected">
        About
      </NavLink>
      <NavLink to="/contact" activeClassName="selected">
        Contact
      </NavLink>
    </>
  );
};

function App() {
  const homeRef = useRef(null);
  const heroRef = useRef(null);
  const aboutRef = useRef(null);
  const contactRef = useRef(null);

  return (
    <div ref={homeRef} className="App">
      <BrowserRouter>
        <Header innerRefs={{ aboutRef, contactRef, heroRef, homeRef }} />
        <Route
          exact
          to="/"
          render={routeProps => (
            <Home
              {...routeProps}
              innerRefs={{ aboutRef, contactRef, heroRef, homeRef }}
            />
          )}
        />
        // More routes here.
      </BrowserRouter>
    </div>
  );
}

export default App;

Home.tsx

import React, { Fragment, forwardRef, useRef } from "react";
import "../styles.css";

const Hero = forwardRef((props, ref) => {
  return (
    <section ref={ref}>
      <h1>Hero Section</h1>
    </section>
  );
});

const About = forwardRef((props, ref) => {
  return (
    <section ref={ref}>
      <h1>About Section</h1>
    </section>
  );
});

const Contact = forwardRef((props, ref) => {
  return (
    <section ref={ref}>
      <h1>Contact Section</h1>
    </section>
  );
});

function Home({ innerRefs }) {
  return (
    <Fragment>
      <Hero ref={innerRefs.heroRef} />
      <About ref={innerRefs.aboutRef} />
      <Contact ref={innerRefs.contactRef} />
    </Fragment>
  );
}

export default Home;

Working demo here

Upvotes: 2

Related Questions