Search code examples
javascriptreactjsgatsbystyled-componentsreact-functional-component

Page Jumps on Component Re-Render (ReactJS/Gatsby)


Describe the Problem

I am making a very simple ReactJS/Gatsby website for someone and I am having an issue with one of my functional styled components when it re-renders. The problem is that it is causing the window to jump (scroll) after the re-render is complete.

The re-render is triggered by the user clicking on a span element (enclosed in an li element) which fires a function.

The list of li elements is determined by the state of the component. The overall parent component has a fixed height which is why I am having trouble diagnosing the issue.

What I Expect to Happen

The component to re-render and the window's scroll position to remain where it was when the user initiated it.

What Actually Happens

When the user clicks the element the page appears to jump (scroll). Sometimes it does so and remains in the new position, sometimes it does so and then returns to the original scroll position.

What I've Tried

I've tried following advice from other questions which suggest using event.preventDefault() and others which suggest moving the styling out of the component itself and, instead, opting for using classes.

Neither of these solutions worked.

I have managed to definitively find that the issue is due to setActiveTabs -- which causes the re-render of the ul element -- as logging window.scrollY both prior to it firing and after it completes displays a different value.

Edit 2:

I have managed to figure out that the issue is with making the list items targetable. It seems that either adding the tabIndex="0" attribute or making the li child an interactive element causes this bug.

Does anyone know a way around this?

Edit

The full frontend source code can be found in the following GitHub repo: https://github.com/MakingStuffs/resinfusion


Solution

  • In order to solve the issue I needed to prevent the clicked element from being targeted on the re-render. In order to do this I edited the clickHandler so that it uses element.blur() after setting the state.

    The click handler is as follows:

    const forwardClickHandler = event => {
        setLoading(true)
        const clickedSlug =
          event.target.closest("button") !== null
            ? event.target.closest("button").getAttribute("data-slug")
            : event.target.children[0].getAttribute("data-slug")
            
        const categoryObject = getNeedle(clickedSlug, categories, "slug")
        const subCatObject = getNeedle(clickedSlug, subCategories, "slug")
        const serviceObject = getNeedle(clickedSlug, services, "slug")
        const associatedChildren = getAssociatedChildren(
          categoryObject
            ? categoryObject
            : subCatObject
            ? subCatObject
            : serviceObject
        )
        setBgImage(associatedChildren[0].thumb.localFile.childImageSharp.fluid)
        setActiveTabs(associatedChildren)
        event.target.blur()
        return setTimeout(() => setLoading(false), 1000)
      }