Alexandre PUJOL
Alexandre PUJOL

Reputation: 140

ReactJs - Useless re-renders when a context changes - HOW TO SOLVE IT?

I'm currently working on this website: https://virgile-hasselmann.vercel.app/. And have an issue I did not managed to solve, I made a custom cursor and with it a CursorContext to change its state when hover, etc... I've noticed that when the context changes it's state, every component of my app rerenders. Why so ? And How to solve it ? I've heard of memoising the component right below the ContextProvider but it doesn't seem to work (the component below the provider is my layout component, no problem with that ?)

Here is the my app component, how can I prevent those useless rerenders to happen ?

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <AuthProvider>
      <CursorProvider>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </CursorProvider>
    </AuthProvider>
  );
}

I've wrapped the layout into a React.memo HOC, didn't work:

export default React.memo(Layout);

I've tried using useMemo in the layout component :

const Layout = ({ children }: Props): JSX.Element => {
  const { width } = useWindowSize();
  const memoizedJSX = useMemo(() => {
    return (
      <div id="App" className="relative">
        <Head>
          <title>Virgile Hasselmann</title>
          <meta
            name="description"
            content="Virgile Hasselmann, a video and photo portfolio"
          />
          <link rel="icon" href="/favicon.ico" />
        </Head>
        {width! > 768 && <CustomCursor />}
        <header className="fixed w-screen z-50">
          <Navbar />
        </header>
        <main className="min-h-screen">
          <div>{children}</div>
        </main>
      </div>
    );
  }, []);
  return memoizedJSX;
};

It didn't work either ... HELP !

Upvotes: 0

Views: 258

Answers (1)

Nathaniel Tucker
Nathaniel Tucker

Reputation: 597

The problem is you are memoizing the piece where you use the context. Any component bound to the context will be re-render when it changes - otherwise how is it supposed to have the new context state?

What you need to do is push the binding of state to only where it it used. So in your case you need to turn layout from

const Layout = ({ children }: Props): JSX.Element => {
  const { width } = useWindowSize();
  const memoizedJSX = useMemo(() => {
    return (
      <div id="App" className="relative">
        <Head>
          <title>Virgile Hasselmann</title>
          <meta
            name="description"
            content="Virgile Hasselmann, a video and photo portfolio"
          />
          <link rel="icon" href="/favicon.ico" />
        </Head>
        {width! > 768 && <CustomCursor />}
        <header className="fixed w-screen z-50">
          <Navbar />
        </header>
        <main className="min-h-screen">
          <div>{children}</div>
        </main>
      </div>
    );
  }, []);
  return memoizedJSX;
};

To

const Layout = ({ children }: Props): JSX.Element => {
    return (
      <div id="App" className="relative">
        <Head>
          <title>Virgile Hasselmann</title>
          <meta
            name="description"
            content="Virgile Hasselmann, a video and photo portfolio"
          />
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <CursorWithWidth/>
        <header className="fixed w-screen z-50">
          <Navbar />
        </header>
        <main className="min-h-screen">
          <div>{children}</div>
        </main>
      </div>
    );
};
const CursorWithWidth = () => {
  const { width } = useWindowSize();
  return {width! > 768 && <CustomCursor />}
}

Now when the Context changes, only CursorWithWidth will re-render. And if you memo CustomCursor - you'll short-circuit that child as well!

In general with components you should be careful about your data bindings. And try to co-locate your data dependencies. This also conveniently cleans up your code making it more readable, and your components more reusable as they focus on less things. (See chapter 2 of Clean Code).

With this in mind, another good practice is to not obfuscate your data bindings with a hook like useWindowSize(). Instead, be clear about where your bindings take place by using the useContext hook directly and at the beginning of the component. You can generally see when a component does too many things when you have a bunch of data binding calls at the beginning. Do you really need all that data in the same place?

Upvotes: 2

Related Questions