devstefancho
devstefancho

Reputation: 2930

selected element move up and down, but scroll behavior is not working with

I make a list of div tags for listing filename. and After selecting a div, then I can change focus up and down using Arrow keys

Because I have a long file list, I add overflow: scroll to the container but Scroll does not move along with my focus(so active div disappear from the viewport), How can I make scroll behavior move down along with active div?

I create an example in codesandbox

import "./styles.css";
import { useEffect, useState } from "react";

export default function App() {
  const [selectedItem, setSelectedItem] = useState(0);

  useEffect(() => {
    const keyPress = (e) => {
      if (e.key === "ArrowLeft") {
        setSelectedItem((prev) => Number(prev) - 1);
      }
      if (e.key === "ArrowRight") {
        setSelectedItem((prev) => Number(prev) + 1);
      }
    };
    window.addEventListener("keydown", keyPress);
    return () => {
      window.removeEventListener("keydown", keyPress);
    };
  }, [selectedItem]);

  const onClickDiv = (e) => {
    setSelectedItem(e.target.id);
  };

  const renderList = () => {
    let items = [];
    console.log(selectedItem);
    for (let i = 0; i < 60; i++) {
      items.push(
        <div
          key={i}
          className={`item ${Number(selectedItem) === i ? "active" : ""}`}
          id={i}
          onClick={onClickDiv}
        >
          Item{i}.png
        </div>
      );
    }
    return items;
  };

  return (
    <div className="App">
      <div className="list-container">{renderList()}</div>
    </div>
  );
}

.list-container {
  height: 300px;
  overflow: scroll;
}

.active {
  background-color: orangered;
}

------------------ EDIT -----------------------

I finally complete this example, I sincerely thank you guys for answering my question.

Here is code sandbox final code

Upvotes: 2

Views: 2622

Answers (2)

Cybershadow
Cybershadow

Reputation: 1167

Here's my take on it.

I am using refs as well along with scrollIntoView.This way we don't have to scroll by a fixed amount and also we only are scrolling when we are at the end of the viewport.

Here's the demo

I am storing refs of each element.

 ref={(ref) => {
            elementRefs.current = { ...elementRefs.current, [i]: ref };
 }}

And then we will use scrollIntoView when focus changes.

const prevItem = elementRefs.current[selectedItem - 1];
prevItem && prevItem.scrollIntoView({ block: "end" });

Notice the {block:"end"} argument here. It makes sure we only scroll if the element is not in the viewport.

You can learn more about scrollIntoView here.

import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import { useEffect, useState, useRef } from "react";

export default function App() {
  const [selectedItem, setSelectedItem] = useState(0);
  const elementRefs = useRef({});
  useEffect(() => {
    const keyPress = (e) => {
      if (e.key === "ArrowLeft") {
        setSelectedItem((prev) => Number(prev) - 1);
        const prevItem = elementRefs.current[selectedItem - 1];
        prevItem && prevItem.scrollIntoView({ block: "end" });
      }
      if (e.key === "ArrowRight") {
        console.log(elementRefs.current[selectedItem]);
        // if (selectedItem < elementRefs.current.length)
        const nextItem = elementRefs.current[selectedItem + 1];
        nextItem && nextItem.scrollIntoView({ block: "end" });
        setSelectedItem((prev) => Number(prev) + 1);
      }
    };

    window.addEventListener("keydown", keyPress);
    return () => {
      window.removeEventListener("keydown", keyPress);
    };
  }, [selectedItem]);

  const onClickDiv = (e) => {
    setSelectedItem(e.target.id);
  };

  const renderList = () => {
    let items = [];
    console.log(selectedItem);
    for (let i = 0; i < 60; i++) {
      items.push(
        <div
          key={i}
          className={`item ${Number(selectedItem) === i ? "active" : ""}`}
          id={i}
          onClick={onClickDiv}
          ref={(ref) => {
            elementRefs.current = { ...elementRefs.current, [i]: ref };
          }}
        >
          Item{i}.png
        </div>
      );
    }
    return items;
  };

  return (
    <div className="App">
      <div className="list-container">{renderList()}</div>
    </div>
  );
}

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




Upvotes: 6

Nisanth Reddy
Nisanth Reddy

Reputation: 6395

One way to approach this problem is to use ref in React.

First define a ref using

const scrollRef = useRef(null);

Then, assign it to your scrolling element like this,

<div className="list-container" ref={scrollRef}>

What this does is, gives you a reference to the html element inside your code.

ref.current is the HTML Div element now.

Now, you can use scrollBy method on HTML element to scroll up or down.

Like this,

useEffect(() => {
  const keyPress = (e) => {
    if (e.key === "ArrowLeft") {
      setSelectedItem((prev) => Number(prev) - 1);
      scrollRef.current.scrollBy(0, -18); // <-- Scrolls the div 18px to the top
    }
    if (e.key === "ArrowRight") {
      setSelectedItem((prev) => Number(prev) + 1);
      scrollRef.current.scrollBy(0, 18);  // <-- Scrolls the div 18px to the bottom
    }
  };
  window.addEventListener("keydown", keyPress);

  return () => {
    window.removeEventListener("keydown", keyPress);
  };
}, [selectedItem]);

I have given 18 because I know the height of my list item.

I have updated your Sandbox. Check it out.

Upvotes: 1

Related Questions