0

I have a navbar with a list of categories, and I wanted to set the selected category according to what the user is looking at, but for that I need to get the element ID, but these IDs are dynamically set. Currently I already have a function to set the selected category, but it sets the selected category as the click, and I would also like to mark the selected one as the user scrolls.

My code

 function onHandleClick(props){
        setCurrentCat(props) 
        document.getElementById(props).scrollIntoView();
    }
    return (
        <div className = {Position ? 'navbarContainer navbarContainerScroll':'navbarContainer'}>
            {Categorias.map(item =>(
               <ul>
                   <li className = {item.Cod === currentCat ? 'navbarContainer liSelect' : 'navbarContainer liNormal'}  
                   onClick = {() => onHandleClick(item.Cod) 
                   }>{item.Nome}</li>
               </ul>
            ))}
  
        </div>
    )

I currently move the screen to the category are clicked on the navigation bar, how can I set the selected category when the screen focus is on top of it

Matheus Martins
  • 139
  • 1
  • 14

1 Answers1

1

I have tried to answer your problem as I understand it. I have filled in some extra pieces to help give a full solution. I hope bits and pieces of this are relevant to your issue!

So if I understand you correctly you want to:

  • be able to automatically update the active tab in your navbar when a user scrolls the page. Also, I assume they are scrolling vertically for my answer.

As per the problem and code you provided, I infer you have a setup somewhat like this:

  • A Section or Category component containing content for a given item
  • A Page component that would render a number of Cateogory components and NavBar

Given these two things, I have put the following code together that could help you achieve the functionality you are looking for:

import React, { useEffect, useRef, useState } from "react";

/** Check if an HTML element is within the main focus region of the page */
function isElementInMainFocusArea(
  element,
  verticalConstrainFactor = 0,
  horizontalConstrainFactor = 0
) {
  const elementRect = element.getBoundingClientRect();

  const documentHeight =
    window.innerHeight || document.documentElement.clientHeight;
  const documentWidth =
    window.innerWidth || document.documentElement.clientWidth;

  // Vertical focus region
  const topFocusPos = documentHeight * verticalConstrainFactor;
  const bottomFocusPos = documentHeight * (1 - verticalConstrainFactor);

  // Horizontal focus region
  const leftFocusPos = documentWidth * horizontalConstrainFactor;
  const rightFocusPos = documentWidth * (1 - horizontalConstrainFactor);

  return (
    elementRect.top >= topFocusPos &&
    elementRect.bottom <= bottomFocusPos &&
    elementRect.left >= leftFocusPos &&
    elementRect.right <= rightFocusPos
  );
}

/** Navigation bar component which will taken in a list of refs.
Each ref must be assigned to or given as a prop to some other section on the page that we want to scroll to. */
function NavBar({ pageSectionRefs, categories }) {
  const [currentCat, setCurrentCat] = useState();

  /** Set up the scroll event listener to update the currentCat whenever we scroll*/
  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  /** Check all the refs we are to watch to see which one of them is in focus */
  function handleScroll() {
    for (ref in pageSectionRefs.reverse()) {
      if (isElementInMainFocusArea(ref.current)) {
        setCurrentCat(index);
        return; // if two elements are in focus, only the first will be chosen. Could cause a glitch
      }
    }
  }

  // Returns what you were previously returning for this component
  return (
    <div
      className={
        Position ? "navbarContainer navbarContainerScroll" : "navbarContainer"
      }>
      {categories.map((item) => (
        <ul>
          <li
            className={
              item.Cod === currentCat
                ? "navbarContainer liSelect"
                : "navbarContainer liNormal"
            }
            onClick={() => onHandleClick(item.Cod)}>
            {item.Nome}
          </li>
        </ul>
      ))}
    </div>
  );
}

/** The top level component that will be rendering the NavBar and page sections. */
function MyPage() {
  const categories = [];
  const pageSectionRefs = categories.map(() => useRef());

  return (
    <div>
      <NavBar pageSectionRefs={pageSectionRefs} categories={categories} />
      {categories.map((cat, idx) => {
        return (
          <div key={idx} ref={pageSectionRefs[idx]}>
            Section for category {idx}
          </div>
        );
      })}
    </div>
  );
}

In case I don't have a full picture of your problem, kindly comment things I may have missed so I update my answer!

References:

rexess
  • 729
  • 4
  • 7