admin管理员组

文章数量:1252854

I am looking to write a React hook with React 16.8.6 that will let me scroll to a particular HTML element section on click of a navigation item. I have a Navigation ponent that is a sibling of the sections rendered on the page.

Also when the page scrolls, I would like to update state of the App with that HTML section.

Navigation Component JSX

<ul class="nav>
   <li><a>Section 1</a></li>
   <li><a>Section 2</a></li>          
</ul>

Sections in Home Page at App Level Component

<section className="section-1">Section 1</section>
<section className="section-2">Section 2</section>

Hooks


const [navItem, setNavItem] = React.useState(null);
const sectionRef = React.useRef(null);

// Scroll To Item
useEffect(() => {
    console.log(sectionRef.current);
    if (sectionRef.current) {
      sectionRef.current.scrollToItem();
    }
}, []);

I am looking to write a React hook with React 16.8.6 that will let me scroll to a particular HTML element section on click of a navigation item. I have a Navigation ponent that is a sibling of the sections rendered on the page.

Also when the page scrolls, I would like to update state of the App with that HTML section.

Navigation Component JSX

<ul class="nav>
   <li><a>Section 1</a></li>
   <li><a>Section 2</a></li>          
</ul>

Sections in Home Page at App Level Component

<section className="section-1">Section 1</section>
<section className="section-2">Section 2</section>

Hooks


const [navItem, setNavItem] = React.useState(null);
const sectionRef = React.useRef(null);

// Scroll To Item
useEffect(() => {
    console.log(sectionRef.current);
    if (sectionRef.current) {
      sectionRef.current.scrollToItem();
    }
}, []);
Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Jun 3, 2019 at 2:11 Mark AMark A 2,1354 gold badges21 silver badges28 bronze badges 2
  • Is your navigation and sections kept in the same ponent file? – Cat_Enthusiast Commented Jun 3, 2019 at 3:04
  • Updated description... The <Nav/> ponent is separate from those sections, but they render on that single page. – Mark A Commented Jun 3, 2019 at 3:30
Add a ment  | 

2 Answers 2

Reset to default 20

If you don't mind using react-router-dom, then you can track history changes and update the scroll position to an HTML element's id via a hash history change. The advantage of this approach is you don't have to utilize state, nor utilize refs, and it can scale across the entire application (regardless of where the elements are located within the application's tree, you can scroll to them).

Working example:

https://fglet.codesandbox.io/ (demo)

https://codesandbox.io/s/fglet (source -- unfortunately, doesn't work within the codesandbox editor)


ponents/ScrollHandler (hook that listens to hash history changes, searches for elements that match the id located within the hash and, if it finds a matching element id, then it'll scroll to the element)

import { useEffect } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";

const ScrollHandler = ({ location }) => {
  useEffect(() => {
    const element = document.getElementById(location.hash));

    setTimeout(() => {
      window.scrollTo({
        behavior: element ? "smooth" : "auto",
        top: element ? element.offsetTop : 0
      });
    }, 100);
  }, [location]);

  return null;
};

ScrollHandler.propTypes = {
  location: PropTypes.shape({
    pathname: PropTypes.string,
    search: PropTypes.string,
    hash: PropTypes.string,
    state: PropTypes.any,
    key: PropTypes.string
  }).isRequired
};

export default withRouter(ScrollHandler);

ponents/Navigation (links to change url hash history location)

import React from "react";
import { Link } from "react-router-dom";
import List from "../List";

const Navigation = () => (
  <List>
    {[1, 2, 3, 4, 5].map(num => (
      <li key={num}>
        <Link to={`/#section${num}`}>Section {num}</Link>
      </li>
    ))}
  </List>
);

export default Navigation;

ponents/Sections (the Headline ponent contains the id that will be matched against)

import React from "react";
import Headline from "../Headline";

const Sections = () =>
  [1, 2, 3, 4, 5].map(num => (
    <Headline key={num} id={`#section${num}`}>
      Section {num}
    </Headline>
  ));

export default Sections;

index.js

import React from "react";
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";

import Container from "./ponents/Container";
import Navigation from "./ponents/Navigation";
import Sections from "./ponents/Sections";
import ScrollHandler from "./ponents/ScrollHandler";
import "./styles.css";

const App = () => (
  <BrowserRouter>
    <Container>
      <ScrollHandler />
      <Navigation />
      <Sections />
    </Container>
  </BrowserRouter>
);

render(<App />, document.getElementById("root"));

I am using React Router V6. Some things didn't work and were different. For instance, withRouter was deprecated. React router offered a solution if you need it (link).

My solution for V6:

Create a ponent WithRouter.jsx:

import { useLocation, useNavigate, useParams } from "react-router-dom";

function withRouter(Component) {
  function ComponentWithRouterProp(props) {
    let location = useLocation();
    let navigate = useNavigate();
    let params = useParams();
    return <Component {...props} router={{ location, navigate, params }} />;
  }

  return ComponentWithRouterProp;
}

export default withRouter;

Create a ponent ScrollHandler.jsx

import { useEffect } from "react";
import WithRouter from "./WithRouter";

const ScrollHandler = ({ location }) => {
  useEffect(() => {
    const element = document.getElementById(location.hash.substring(1));

    if (element) element.scrollIntoView();
  }, [location]);

  return null;
};

export default WithRouter(ScrollHandler);

In index.js I wrapped my <App/> p with the BrowserRouter as Router like so:

<Router>
   <App />
</Router>

Then in App.js, add the <ScrollHandler/> ponent:

<ScrollHandler location={location} />

本文标签: javascriptReact Hooks Scroll to ElementStack Overflow