When the length of a page changes, is there a way to scroll up or down smoothly instead of jumping?

57 views Asked by At

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

There are 2 answers

1
passingThru On

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

1
Gray On

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>
    )
}