Kim-Jun-Un
Kim-Jun-Un

Reputation: 83

How to scroll to specific point on DOM using useRef()

I am trying to access a specific point on DOM using useRef() from a dropdown.
at the moment I have implemented the logic using # ID but I want to use useRef().

Also to be mentioned: I don't have access to the App.js to pass the ref to other components.

dropdown.js

export default function DropDown() {
  const ScrollToTitle = (e) => {
    const title = e.target.value;
    window.location = '#' + title;
// how to access the ref in here insted of # ID?
  };
  return (
    <select onChange={ScrollToTitle}>
      <option>First </option>
      <option>Second </option>
      <option>Third </option>
      <option>Another </option>
      <option>Somthing </option>
    </select>
  );
}

I have a couple of titles on the App but as an example here is one of them:

export default function First() {

  const titleRef = useRef(); // what I wan to use

  return (
    <div id='First'>
      <h1 ref={titleRef}>First </h1> // is this correct ?
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
    </div>
  );
}

How and from where to access the titleRef?
to be mentioned again : I don't have access to the App.js to pass the ref to other components.

and I have a live example here: https://stackblitz.com/edit/react-cpezfb

Thanks a lot

Upvotes: 0

Views: 946

Answers (2)

Houssam
Houssam

Reputation: 1877

You can scroll to an element using its ref (such as titleRef) by doing the following:

titleRef.current.scrollIntoView();

A more complete solution to your particular issue is here, where I updated your code to work for the First and Second components.

The idea is that the refs to the components you want to scroll to should be shared, thus defined in the parent. The refs should be assigned to the different components, then the dropdown can make use of them for the navigation.

Ideally, you should avoid the use of the strings "First", "Second" etc as keys and use constants for instance (by defining the values as constant such as const FIRST = "First"; then using those constants so that your code is easier to maintain.

Upvotes: 1

Nick Vu
Nick Vu

Reputation: 15540

Your problem with useRef is you did not share the mutual states between section components and the dropdown list component, so if you want to call useRef, you need to share that with all your related components.

Here is the full implementation of how to call useRef for your case

Firstly, I used forwardRef to pass the ref to a specific element in your section components (in this case, the ref is the title element). If you don't understand what it is, you can read this doc

import React, { useRef } from 'react';

export default React.forwardRef(function First(props, ref) {
  return (
    <div id='First'>
      <h1 ref={ref}>First </h1>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
      <p>Start editing to see some magic happen :)</p>
    </div>
  );
})

And then in App.js, I defined useRef for a list of refs (not single) for all sections. I also used liftting state up technique for scrollToTitle

import React, { useRef } from 'react';
import './style.css';
import First from './components/First';
import Second from './components/Second';
import Third from './components/Third';
import Another from './components/Another';
import Somthing from './components/Somthing';
import DropDown from './components/DropDown';

export default function App() {
  const titleRefs = useRef({});
  const scrollToTitle = (e) => {
    const title = e.target.value;
    titleRefs.current[title].scrollIntoView();
  };
  return (
    <div>
      <h1>Hello StackBlitz!</h1>
      <DropDown scrollToTitle={scrollToTitle} />
      <First ref={(node) => (titleRefs.current['First'] = node)} />
      <Second ref={(node) => (titleRefs.current['Second'] = node)} />
      <Third ref={(node) => (titleRefs.current['Third'] = node)} />
      <Another ref={(node) => (titleRefs.current['Another'] = node)} />
      <Somthing ref={(node) => (titleRefs.current['Somthing'] = node)} />
    </div>
  );
}

BUT I think you used window.location = '#' + title; is not that bad. With the above implementation, your URL won't have #id to access that section directly by URL, so many of us still prefer using # instead of ref.

Upvotes: 1

Related Questions