Search code examples
reactjstypescripttailwind-css

Stop scroll position from moving to the bottom


The code i added is just an example to show my problem in a simpler way. But the idea is that i have this titles array that gets looped through and shown on screen and when you click on a title its sub-titles show up below. Kinda like a drop down element. The problem is when i for example first select the first title and then scroll down to the eight title and click on it the last sub-title elements become visible and i have to scroll back up to see the top ones. How can i stop scroll position from jumping to the bottom when i click on a title element.

const Hold = () => {
  interface TitleItem {
    id: number;
    title: string;
  }

  interface SubTitleItem {
    id: number;
    subTitle: string;
  }

  const titleArray: TitleItem[] = [
    { id: 1, title: "First Item" },
    { id: 2, title: "Second Item" },
    { id: 3, title: "Third Item" },
    { id: 4, title: "Fourth Item" },
    { id: 5, title: "Fifth Item" },
    { id: 6, title: "Sixth Item" },
    { id: 7, title: "Seventh Item" },
    { id: 8, title: "Eighth Item" },
    { id: 9, title: "Ninth Item" },
    { id: 10, title: "Tenth Item" },
  ];

  const subTitleArray: SubTitleItem[] = [
    { id: 1, subTitle: "First Sub Item" },
    { id: 2, subTitle: "Second Sub Item" },
    { id: 3, subTitle: "Third Sub Item" },
    { id: 4, subTitle: "Fourth Sub Item" },
    { id: 5, subTitle: "Fifth Sub Item" },
    { id: 6, subTitle: "Sixth Sub Item" },
    { id: 7, subTitle: "Seventh Sub Item" },
    { id: 8, subTitle: "Eighth Sub Item" },
    { id: 9, subTitle: "Ninth Sub Item" },
    { id: 10, subTitle: "Tenth Sub Item" },
  ];

  const [selectedTitle, setSelectedTitle] = useState<TitleItem | null>(null);

  return (
    <div className="p-10 h-screen">
      {titleArray.map((title) => (
        <div key={title.id}>
          <div
            onClick={() => {
              setSelectedTitle(title);
            }}
          >
            {title.title}
          </div>
          {selectedTitle && selectedTitle.id === title.id && (
            <div>
              {subTitleArray.map((subTitle) => (
                <div className="h-28 border p-5 mb-2" key={subTitle.id}>
                  {subTitle.subTitle}
                </div>
              ))}
            </div>
          )}
        </div>
      ))}
    </div>
  );
};

export default Hold;

Thanks in advance.


Solution

  • This is the expected behaviour. There's that bit of a scroll because the previously selected title has now collapsed, and the DOM has to account for the space previously occupied by the sub menu items of the previously selected item, hence collapsing, and resulting in a change of scroll position.

    To prevent this, my approach would be to track all the selected titles so that a title is only explicitly collapsed. This means that all ids of selected titles are stored in an array, so that the selection of a new title does not result in a deselection of a previous title, and conversely does not result in the "weird" window scroll.

    Hence the state will look something like this:

    const [selectedTitles, setSelectedTitles] = useState<Array<number>>([]);
    

    And the rest of the body will look like this:

      return (
        <div className='p-10 h-screen overflow-y-auto'>
          {titleArray.map(title => (
            <div key={title.id}>
              <div
                onClick={() => {
                  if (selectedTitles.includes(title.id)) {
                    setSelectedTitles(selectedTitles.filter((selectedTitle) => selectedTitle !== title.id))
                  }
                  else {
                    setSelectedTitles([
                      ...selectedTitles,
                      title.id,
                    ]);
                  }
                }}
              >
                {title.title}
              </div>
              {selectedTitles.includes(title.id) && (
                <div>
                  {subTitleArray.map(subTitle => (
                    <div className='h-28 border p-5 mb-2' key={subTitle.id}>
                      {subTitle.subTitle}
                    </div>
                  ))}
                </div>
              )}
            </div>
          ))}
        </div>
      );