React using refs to scroll to when selecting from a dropdown

77 views Asked by At

Ok, I am trying to create a dropdown and when selecting an item, the page should scroll to a specific section (ref).

I have several divs with the same ref (as I have more than 30 divs that require a ref).

const SettingView = () => {
    const selectedItem = "el2"; // This is temporarily as it will be the item from a dropdown
    const ref = useRef<HTMLDivElement[]>([]);
    const filterRef = (el: HTMLDivElement) => ref.current.push(el);

    return (
        <>
            for (let item of sortedRows) {
                <div ref={filterRef} id={item.some.name}>{item.text}</div>
            }
        </>
    )
}

export default SettingView;

So on a button click it should find the div ref that has the id from the selectedItem and scroll to it.

How would I do that?

3

There are 3 answers

1
WestMountain On BEST ANSWER

https://stackblitz.com/edit/stackblitz-starters-tuw9cm?file=src%2FApp.tsx

import { FC, useEffect, useRef, useState } from 'react';

const Div = ({ text, isActive }) => {
  const ref = useRef<any>();
  useEffect(() => {
    if (isActive) {
      ref.current.scrollIntoView({ behavior: 'smooth' });
    }
  }, [isActive]);
  return (
    <div
      ref={ref}
      style={{
        background: isActive ? '#006699' : '#dddddd',
        height: '200px',
        margin: '10px',
      }}
    >
      {text}
    </div>
  );
};

export const App: FC<{}> = () => {
  const data = [1, 2, 3, 4, 5, 6, 7];
  const [activei, setActivei] = useState(5);
  return (
    <div>
      <div>
        {data.map((item) => (
          <div
            onClick={() => {
              setActivei(item);
            }}
            key={item}
          >
            {' '}
            {item}{' '}
          </div>
        ))}
      </div>
      {data.map((item, index) => (
        <Div isActive={item === activei} text={item} key={item} />
      ))}
    </div>
  );
};

1
Rajab Ali On

You can achieve this by using the scrollIntoView() method on the selected element's ref. Here's how you can modify your SettingView component to achieve this functionality:

import React, { useRef } from 'react';

const SettingView = () => {
    const selectedItem = "el2";
    const ref = useRef<HTMLDivElement[]>([]);
    const filterRef = (el: HTMLDivElement) => ref.current.push(el);

    const scrollToRef = (ref: React.RefObject<HTMLDivElement>, id: string) => {
        const selectedRef = ref.current.find(item => item.id === id);
        if (selectedRef) {
            selectedRef.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
    };

    React.useEffect(() => {
        scrollToRef(ref, selectedItem);
    }, [selectedItem]);

    return (
        <>
            <div ref={filterRef} id="el1">Some Text</div>
            <div ref={filterRef} id="el2">Some Text2</div>
            <div ref={filterRef} id="el3">Some Text3</div>
            <div ref={filterRef} id="el4">Some Text4</div>
        </>
    );
}

export default SettingView;

In this code: scrollToRef function takes the ref array and the id of the selected item. It finds the ref with the matching id and scrolls it into view using scrollIntoView() with options for smooth behavior and starting from the block's start. useEffect hook is used to trigger the scrolling effect whenever selectedItem changes.

8
0stone0 On

If you render your buttons using a map(), which should be fine since only the id differs, then you can use the iterator of map to use that as an id.

Then when clicking the button, get that index in the ref.current array and call (eg) scrollIntoView() on that.


Example:

  • Click on a button to call scrollIntoView on that
  • Click on the uppermost (real) button to call scrollIntoView on index 50

const { useState, useRef } = React;

const SettingView = () => {

    const ref = useRef([]);
    
    let sortedRows = [
        { name: 'Foo', text: 'Bar' }
    ];
    
    // Add 99 other rows to sortedRow as debug values
    sortedRows = sortedRows.flatMap(i => Array(99).fill(i));
    
    const onButtonClick = (i) => {
        console.info('Clicked on ', i);
        if (ref.current[i]) {
            ref.current[i].scrollIntoView();
        }
    }
    
    return (
      <React.Fragment>
        <button onClick={() => onButtonClick(50)}>Scroll to Button #50</button>
        
        {sortedRows.map((row, i) => {
            const { name, text } = row;
            
            return (
                <div onClick={() => onButtonClick(i)} ref={el => ref.current[i] = el} id={"el" + i}>
                    Button {i} - {text}
                </div>
            );
            
        })}
      </React.Fragment>
     );
}

ReactDOM.render(<SettingView />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>