skip to Main Content

I am working on one small part where I have to show some divs horizontally, so when it goes out of the space I am using css as overflow-x:auto so that it becomes scrollable.

What I did

<div className="App d-flex">
  {data.map((li) => {
    return (
      <div key={li.id} className="boxes">
        {li.name}
      </div>
    );
  })}
</div>

My CSS below

.d-flex {
  display: flex;
}
.boxes {
  padding: 12px;
  margin: 4px;
  background-color: rgb(196, 218, 243);
  min-width: 150px;
  overflow-x: auto;
  text-align: center;
  border-radius: 6px;
}

What I am trying to do

  • I want to put dots there so that when user clicks on dots it should scroll.
  • the dots should trigger only when exceeded a number of items, means if there are less items which can be shown on one screen then no dots or else dots.
  • Here I am not getting the idea how can I do this using react-hooks.

Here is my Code SandBox

Edit
I am trying to get This

I want to show number of dots for each page , suppose on one page there are four divs so these can be shown in one page and no dots will be there, but when divs are 8 so it will come in two pages or three so 2-3 dots will come and when i click it should scroll to respective page as shown in picture.

4

Answers


  1. Solve the problem first. Code next.

    Solution

    Observe the container div, the first box’s div, and the last box’s div and change the UI accordingly.

    Keep booleans (simple variables are fine, state not needed) to check if the left button and right button are visible or not, by comparing against the container (if they are sticking or not).

    Code (idea)

    Use ref to get the DOM nodes, (instead of document.getElem..., you know). Get their position and dimensions using myRef.current.getBoundingClientRect().

    Pseudocode:

    <div ref={containerRef}>
      {myArray.map((item, idx) => <div ref={idx === 0 firstBoxRef ? (idx === myArray.length - 1 ? lastBoxRef : null)}>...</div>}
    </div>
    

    You’ll need to add the buttons, overflow, max-width properties and the onClick scroll, of course.

    PS: One purpose of ref is to act as an alternative to document.getElementById.

    Login or Signup to reply.
  2. You’ll need:

    • An outer container to stay the width of the viewport and allow scrolling.
    • An inner container to grow with the content and overflow the outer div.

    CSS is as follows

    .outer-container {
      width: 100%;
      overflow-x: auto;
    }
    
    .inner-container {
      width: max-content;
    }
    

    Use the Resive Observer API to listen for when either container gets resized. The outer container will be resized when the viewport size changes, while the inner container will be resized when the content changes.

    Then just show the navigation menu if the inner container is larger than the outer container.

      useEffect(() => {
        const outerContainer = outerContainerRef.current;
        const innerContainer = innerContainerRef.current;
        if (!outerContainer || !innerContainer) return;
    
        const observer = new ResizeObserver(() => {
          setShowNavigation(innerContainer.clientWidth > outerContainer.clientWidth);
        });
    
        observer.observe(outerContainer);
        observer.observe(innerContainer);
    
        // Cleanup
        return () => observer.disconnect();
      }, [outerContainerRef.current, innerContainerRef.current]);
    

    In the example below, I use arrows and just scroll left and right to simplify. Making those dots is more complex since it involves calculating how many dots to show, and keeping track of their state. I’m sure you can figure it out from there.


    Snippet Example

    let data = [
      {
        name: 'steve',
        id: 1,
      },
      {
        name: 'Matt',
        id: 2,
      },
      {
        name: 'michel',
        id: 3,
      },
      {
        name: 'Morgan',
        id: 4,
      },
      {
        name: 'joss',
        id: 5,
      },
      {
        name: 'stephen',
        id: 6,
      },
      {
        name: 'clark',
        id: 7,
      },
      {
        name: 'Simmons',
        id: 8,
      },
      {
        name: 'Chris',
        id: 9,
      },
      {
        name: 'Beven',
        id: 10,
      },
    ];
    
    function App() {
      const outerContainerRef = React.createRef();
      const innerContainerRef = React.createRef();
      const [showNavigation, setShowNavigation] = React.useState(false);
      const [num, setNum] = React.useState(3);
    
      React.useEffect(() => {
        const outerContainer = outerContainerRef.current;
        const innerContainer = innerContainerRef.current;
        if (!outerContainer || !innerContainer) return;
    
        const observer = new ResizeObserver(() => {
          setShowNavigation(innerContainer.clientWidth > outerContainer.clientWidth);
        });
    
        observer.observe(outerContainer);
        observer.observe(innerContainer);
    
        // Cleanup
        return () => observer.disconnect();
      }, [outerContainerRef.current, innerContainerRef.current]);
    
      function scrollRight() {
        outerContainerRef.current.scrollBy(outerContainerRef.current.clientWidth, 0);
      }
    
      function scrollLeft() {
        outerContainerRef.current.scrollBy(-outerContainerRef.current.clientWidth, 0);
      }
    
      function add() {
        setNum((num) => Math.min(num + 1, data.length));
      }
    
      function remove() {
        setNum((num) => Math.max(num - 1, 0));
      }
    
      return (
        <div>
          <div className="outer-container d-flex" ref={outerContainerRef}>
            <div className="inner-container d-flex" ref={innerContainerRef}>
            {data.slice(0, num).map((li) => {
              return (
                <div key={li.id} className="boxes">
                  {li.name}
                </div>
              );
            })}
            </div>
          </div>
          <div className="arrow-container">
            {showNavigation && (
              <React.Fragment>
                <button className="left-arrow" onClick={scrollLeft}>
                  {'<'}
                </button>
                <button className="right-arrow" onClick={scrollRight}>
                  {'>'}
                </button>
              </React.Fragment>
            )}
          </div>
          <button onClick={add}>ADD</button>
          <br />
          <br/>
          <button onClick={remove}>REMOVE</button>
        </div>
      );
    }
    
    
    const rootElement = document.getElementById('root');
    const root = ReactDOM.createRoot(rootElement);
    root.render(<App />);
    .d-flex {
      display: flex;
    }
    
    .outer-container {
      width: 100%;
      overflow-x: auto;
    }
    
    .inner-container {
      width: max-content;
    }
    
    .boxes {
      padding: 12px;
      margin: 4px;
      background-color: rgb(196, 218, 243);
      min-width: 150px;
      text-align: center;
      border-radius: 6px;
    }
    
    .arrow-container {
      height: 2em;
    }
    
    .right-arrow {
      position: absolute;
      right: 8px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>

    Stackblitz Typescript example

    https://stackblitz.com/edit/react-ts-rq7fnm?file=App.tsx

    Login or Signup to reply.
  3. Try using Swiper, it covers this use case with its web components.

    Here is the samples:

    Using dots:
    https://codesandbox.io/p/sandbox/uney2m?file=%2Findex.html

    Using dynamic dots:
    https://codesandbox.io/p/sandbox/49kqyp?file=%2Findex.html

    Use the pagination clickable props to enable the functionality of moving to respective slide as shown in this example
    https://codesandbox.io/p/sandbox/xp5tzz?file=%2Findex.html

    Feel free to explore other types
    https://swiperjs.com/demos

    Login or Signup to reply.
  4. You can use a react-magic-slider-dots npm package and use your own CSS to match your design.
    I have implemented this for your reference. Please have a look at the working code sandbox example.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search