javascriptreactjstypescriptreact-hooksuse-ref

Using useRef hook for scroll detection causing re-rendering


I am using useRef hook in a layout component to detect the scroll value of it's children (pageContent). I am then conditionally displaying a button to scroll back to top.

Functionalities are working as excepted, but when I console.log something inside the pageComponent children (<route.component/>), it's shows me that the component is re-rendering each time the button shows / hide (scrollTop Value crosses 500)

Here is my Layout component :

function Layout() {
    const [showBackToTopButton, setShowBackToTopButton] = useState(false);
    const pageContentRef = useRef<HTMLDivElement>(null);

    function handleScroll() {
        const mainContent = pageContentRef.current;
        if (mainContent) {
            if (mainContent.scrollTop > 500) {
                setShowBackToTopButton(true);
            } else {
                setShowBackToTopButton(false);
            }
        }
    }

    function handleClickBackTop() {
        const mainContent = pageContentRef.current;
        if (mainContent) mainContent.scrollTo({ top: 0, behavior: 'smooth' });
    }

    useEffect(() => {
        const mainContent = pageContentRef.current;
        if (mainContent)
            mainContent.onscroll = function () {
                handleScroll();
            };
    }, []);

    return (
        <>
            <div className='drawer drawer-mobile'>
                <input
                    id='left-sidebar-drawer'
                    type='checkbox'
                    className='drawer-toggle'
                />
                <PageContent pageContentRef={pageContentRef} />
                <LeftSidebar />
            </div>

            {showBackToTopButton && (
                <Button
                    className='back-to-top-button absolute right-0 bottom-10'
                    id='back-to-top'
                    onClick={handleClickBackTop}
                >
                    <i className='fa-solid fa-arrow-up' />
                </Button>
            )}
        </>
    );
}

export default Layout;

and here is the PageContent component :

interface PageContentProps {
    pageContentRef: React.RefObject<HTMLDivElement>;
}

function PageContent({ pageContentRef }: PageContentProps) {
    return (
        <div className='drawer-content flex flex-col '>
            <Header />
            <main className='flex-1 overflow-y-auto pt-8 px-6' ref={pageContentRef}>
                <Suspense fallback={<SuspenseContent />}>
                    <Routes>
                        {routes.map((route, key) => {
                            return (
                                <Route
                                    key={key}
                                    path={`${route.path}`}
                                    element={<route.component name={route.name} />}
                                />
                            );
                        })}

                        {/* Redirecting unknown url to 404 page */}
                        <Route path='*' element={<NotFound />} />
                    </Routes>
                </Suspense>
            </main>
        </div>
    );
}

export default PageContent;

Is it a normal behaviour considering the useState value ?

I tried using useMemo but I did not manage to solve my issue.

It's not a very big deal since the component are not rendering tenths of times, but it would be cleaner if they could render only once...


Solution

  • I found the solution wrapping my PageContent component inside a memo

    const PageContent = React.memo(({ pageContentRef }: PageContentProps) => {
        return (
            // ...
        );
    });
    

    Children are now rendered only once. Hope it can help ohter people 🙂