skip to Main Content

I am trying to create a component where there are two tabs, one is a table that displays data, and the other lets the user view more in depth information on specific entries in the table. The issue I am having is that the component is at the bottom of a page, and the info tab is shorter than the table tab, so when I switch between them it shortens the page and jumps instead of scrolling smoothly.

Is there a way to have the page scroll instead of jumping like this, and if that is impossible, is there at least a way to have it scroll before rendering the change?

I tried using useLayoutEffect to scroll before switching, but it only works when going from the larger table tab to the shorter info tab. This is what I have so far:

import React, { useState, useRef, useLayoutEffect } from "react";
import { Button } from "react-bootstrap";
import '../.css'

export default function Example(props) {
    const fields = ["ID", "A count", "B count", "diff"]

    const data = [{ id: 1, aCount: "data", bCount: "data", diff: "difference" },
    { id: 2, aCount: "data", bCount: "data", diff: "difference" },
    { id: 3, aCount: "data", bCount: "data", diff: "difference" },
    { id: 4, aCount: "data", bCount: "data", diff: "difference" },
    { id: 5, aCount: "data", bCount: "data", diff: "difference" },
    { id: 6, aCount: "data", bCount: "data", diff: "difference" },
    { id: 7, aCount: "data", bCount: "data", diff: "difference" },
    { id: 8, aCount: "data", bCount: "data", diff: "difference" },
    { id: 9, aCount: "data", bCount: "data", diff: "difference" },]

    const [isTableVisible, setIsTableVisible] = useState(true)
    const [infoData, setInfoData] = useState(data[0])

    const tabRef = useRef()
    const isMounted = useRef(false);

    useLayoutEffect(() => {
        if (isMounted.current) {
            tabRef.current.scrollIntoView()
        } else {
            isMounted.current = true;
        }
    }, [isTableVisible]);

    function handleTabClick() {
        setIsTableVisible(!isTableVisible)
    }
    function handleViewClick(index) {
        setIsTableVisible(false)
        setInfoData(data[index])
    }

    return (
        <div>
            <div className="ref" id="table" ref={tabRef} />

            {isTableVisible ?
                <div>
                    <span className="tab-active"> Table </span><span className="tab" onClick={handleTabClick}> Info </span>
                </div>
                : <div>
                    <span className="tab" onClick={handleTabClick}> Table </span><span className="tab-active"> Info </span>
                </div>}
                
            {isTableVisible ?
                <div>
                    <table className="data-table">
                        <thead>
                            <tr>
                                {fields.map((field, i) => <th key={field}>{field}</th>)}
                            </tr>
                        </thead>
                        <tbody>
                            {data.map((currData, i) =>
                                <tr key={i}>
                                    <td>{currData.id}
                                        <br />
                                        <Button
                                            variant="btn btn-outline-primary"
                                            onClick={(e => handleViewClick(i))}>
                                            View
                                        </Button>
                                    </td>
                                    <td>
                                        {currData.aCount}
                                    </td>
                                    <td>
                                        {currData.bCount}
                                    </td>
                                    <td >
                                        {currData.diff}
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </table>

                </div>
                : <div className="info-section">
                        Box for Data    
                </div>
            }
        </div>
    )
}

2

Answers


  1. Chosen as BEST ANSWER

    Found a way to scroll up before rendering the change. I wrote a promise to check if the scroll was complete, and only then does the tab switch. this solved the issue I was having. here is the solution:

    import React, { useState, useRef, useLayoutEffect } from "react";
    import { Button } from "react-bootstrap";
    import '../components.css'
    
    export default function Example(props) {
        const fields = ["ID", "A count", "B count", "diff"]
    
        const data = [{ id: 1, aCount: "data", bCount: "data", diff: "difference" },
        { id: 2, aCount: "data", bCount: "data", diff: "difference" },
        { id: 3, aCount: "data", bCount: "data", diff: "difference" },
        { id: 4, aCount: "data", bCount: "data", diff: "difference" },
        { id: 5, aCount: "data", bCount: "data", diff: "difference" },
        { id: 6, aCount: "data", bCount: "data", diff: "difference" },
        { id: 7, aCount: "data", bCount: "data", diff: "difference" },
        { id: 8, aCount: "data", bCount: "data", diff: "difference" },
        { id: 9, aCount: "data", bCount: "data", diff: "difference" },]
    
        const [isTableVisible, setIsTableVisible] = useState(true)
        // const [infoData, setInfoData] = useState(data[0])
    
        const tabRef = useRef()
        const isMounted = useRef(false);
    
        async function delay(){
            var element = document.getElementById("table-ref")    
            var start = Date.now()    
            return new Promise(resolve=>{  
                function scrollWait() {
                    if(Math.abs((window.scrollY - element.offsetTop)) < 0.5){
                        return resolve()
                    }
                    else if(Date.now() > start + 500){
                        return resolve()
                    }
                    else{
                        window.setTimeout(scrollWait, 90)
                    }
                }
                scrollWait()
            }
            )       
        }
    
        function handleTabClick() {
            setIsTableVisible(!isTableVisible)
        }
        function handleViewClick(index) {
            tabRef.current.scrollIntoView()
            delay().then(e=> setIsTableVisible(false))
        }
    
        return (
            <div>
                <div className="ref" id="table-ref" ref={tabRef} />
    
                {isTableVisible ?
                    <div>
                        <span className="tab-active"> Table </span><span className="tab" onClick={handleTabClick}> Info </span>
                    </div>
                    : <div>
                        <span className="tab" onClick={handleTabClick}> Table </span><span className="tab-active"> Info </span>
                    </div>}
                    
                {isTableVisible ?
                    <div>
                        <table className="data-table">
                            <thead>
                                <tr>
                                    {fields.map((field, i) => <th key={field}>{field}</th>)}
                                </tr>
                            </thead>
                            <tbody>
                                {data.map((currData, i) =>
                                    <tr key={i}>
                                        <td>{currData.id}
                                            <br />
                                            <Button
                                                variant="btn btn-outline-primary"
                                                onClick={(e => handleViewClick(i))}>
                                                View
                                            </Button>
                                        </td>
                                        <td>
                                            {currData.aCount}
                                        </td>
                                        <td>
                                            {currData.bCount}
                                        </td>
                                        <td >
                                            {currData.diff}
                                        </td>
                                    </tr>
                                )}
                            </tbody>
                        </table>
    
                    </div>
                    : <div className="info-section">
                            Box for Data    
                    </div>
                }
            </div>
        )
    }
    
    

  2. Well yes, if you use the parameters to specify it…

    element.scrollIntoView({behavior:"smooth", block:"end", inline:"nearest"});
    

    There’s also the scroll() function with parameters in case you need it…

    document.body.scroll({top:0, behavior:"smooth"});
    

    More detail for options here…

    https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

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