Dansekongen
Dansekongen

Reputation: 338

Scrolling components into view with react with useRef

I'm trying to get the onclick handlers in my navbar to scroll different components into view. I have found a way that works, but I'm getting a warning from react, as well as the code is not looking very clean. I'm hoping someone knows how to solve this is in a more correct way, so that the warning goes away and also keeps the code clean.

I'm going to create a scrollTo on each component, so in the Header.js there will be almost similar calls, with different names only.

Here is the structure of the app:

App.js

const App = () => {
    return (
        <div>
            <Header />
            <Banner />
            <About />
            <Technology />
            <Contact />
            <Portfolio />
        </div>
    );
};

Technology.js

const Technology = () => {
    return (
        <section className="technology">
            <div className="heading white">
                <h2>Technologies</h2>
                <p>Some techno</p>
            </div>
        </section>
    )
   }

Header.js (Navbar)

const Header = () => {
    let technology;

useEffect(() => {
        technology = document.querySelector(".technology");
    }, []);

    return (
        <header>
            <p className="logo">Portfolio</p>
            <div className="toggle"></div>
            <ul>
                <li>
                    <p onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}>
                        Home
                    </p>
                </li>
                <li>
                    <p>About Me</p>
                </li>
                <li>
                    <p onClick={() => technology.scrollIntoView({ behavior: "smooth" })}>
                        Technologies
                    </p>
                </li>
                <li>
                    <p>Contact</p>
                </li>
                <li>
                    <p>Projects</p>
                </li>
            </ul>
        </header>
    );
};

Here is the warning:

Assignments to the 'technology' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect

To solve this I guess I need to pass it from App.js to Header.js, and get it from each Component to App.js? My googling skills have not been sufficient in solving this unfortunately.

I know there is a library for this in react, but I was hoping to solve this in a more "native" way for now.

Upvotes: 2

Views: 2306

Answers (2)

lawrence-witt
lawrence-witt

Reputation: 9354

You can do this quite flexibly with function refs, which will allow elements to register themselves onto a universal ref object. Refs are definitely the way to go; you want to avoid accessing the DOM directly whenever possible when using React.

const { useRef } = React;

function App() {
  const pageRefs = useRef({});

  return (
    <div className="app">
      <Header pageRefs={pageRefs} />
      <About pageRefs={pageRefs} />
      <Technology pageRefs={pageRefs} />
      <Portfolio pageRefs={pageRefs} />
    </div>
  );
};

function Header({ pageRefs }) {

  function scrollIntoView(type) {
    pageRefs.current[type].scrollIntoView({ behavior: "smooth" });
  };

  return (
    <header>
      <button onClick={() => scrollIntoView('about')}>
        About
      </button>
      <button onClick={() => scrollIntoView('techno')}>
        Technology
      </button>
      <button onClick={() => scrollIntoView('portfolio')}>
        Portfolio
      </button>
    </header>
  );
};

function About({ pageRefs }) {
  return (
    <section
      className="page about"
      ref={el => pageRefs.current = { ...pageRefs.current, about: el }}>
      About
    </section>
  );
};

function Technology({ pageRefs }) {
  return (
    <section
      className="page techno"
      ref={el => pageRefs.current = { ...pageRefs.current, techno: el }}>
      Technology
    </section>
  );
};

function Portfolio({ pageRefs }) {
  return (
    <section
      className="page portfolio"
      ref={el => pageRefs.current = { ...pageRefs.current, portfolio: el }}>
      Portfolio
    </section>
  );
};

ReactDOM.render(<App />, document.getElementById('root'));
* {
  padding: 0;
  margin: 0;
}

.app {
  width: 100%;
}

.page {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.about {
  background-color: crimson;
}

.techno {
  background-color: lemonchiffon;
}

.portfolio {
  background-color: cornflowerblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 2

Kirill Skomarovskiy
Kirill Skomarovskiy

Reputation: 1575

You can use callback for this.

export const scrollIntoView = (selector, option) => (event) => {
  const el = document.querySelector(selector);

  if (!el) return;

  el.scrollIntoView(option);
};

Usage.

import { scrollIntoView } from 'path/to/file';

const scrollIntoViewTechnology = scrollIntoView('.technology', { behavior: "smooth" });

const Component = () => {
  return (
    <button type='button' onClick={scrollIntoViewTechnology}>Lorem Ipsum</button>
  );
};

Upvotes: 0

Related Questions