Component Based Carlos

Using Children Props to Prevent Rerenders in React Js

By: Carlos Cerda

Category:

I was creating a React JS app that was running into unnecessary re-renders.  What that means is that my app was re-rendering child components that one would consider to be static or unaffected by any changes made in other parts of the app. In this specific app, I was using the React Context API to delegate access to my app’s state and setState/dispatch to specific elements without the need for prop drilling 

My first instinct was to use React.memo to prevent the re-renders happening to the child components. While this does get the job done, it adds another layer of complexity to the app and isn’t the most efficient way of getting this done.

I came across some discussions on this and ran into this article by Kent C. Dodds where he discusses how to prevent unnecessary rendering of  components simply by following idiomatic React concepts. Idiomatic React in this case meaning that you use React as it was intended to be used, by composing elements together.

The Reason Why these Re-Renders Occur

To summarize what the article discussed, the reason why these unnecessary re-renders occur is because of what happens when React creates an element (specifically via React.createElement). The createElement function creates an object where everything is a new instance, including the element’s “props” object. React needs to re-render the component because its unsure if something in the actual dom has changed since the props object is “new”. This becomes an issue for components that don’t follow idiomatic React approaches, specifically the idiom of making components composable.

An Example For Clarity

In the article I mentioned above, the author gave a use case to showcase the difference between making an element composable vs non-composable, but being the type of learner that I am, I needed to create my own example to better understand what was going on. Below is my example. The biggest difference here is how the components are being grouped together. In the “good example render” , the context provider is wrapped in a wrapper component and uses the props.children argument to render any children components. This is the main factor in preventing unnecessary re-renders! Since we use props.children, we are essentially passing the same instance of the child components each time the wrapper component re-renders. This means that React’s shallow comparison logic will label those elements as “same” and wont render them again. The only way those child component elements will render again is if something inside the component itself causes a rerender, see Child1 component button click, and that wont affect other components around it, see Child2.

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const AppStateContext = React.createContext();
const AppDispatchContext = React.createContext();
const AppProvider = ({ children }) => {
  const [state, setState] = React.useState(1);
  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={setState}>
        {children}
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
};

const useAppState = () => {
  const state = React.useContext(AppStateContext);
  if (!state) {
    return Error("Not wrapped in AppStateContext.Provider");
  }
  return state;
};

const useAppDispatch = () => {
  const dispatch = React.useContext(AppDispatchContext);
  if (!dispatch) {
    return Error("Not wrapped in AppStateContext.Provider");
  }
  return dispatch;
};

const Child1 = () => {
  const state = useAppState();
  const dispatch = useAppDispatch();
  console.log("child1");
  return (
    <div>
      {state}
      <button onClick={() => dispatch(state + 1)}>Increment</button>
    </div>
  );
};
const Child2 = () => {
  console.log("child2");
  return <div>minding my own business</div>;
};

// good render, because AppProvider uses idiomatic react to create a context provider and use the prop children argument
// to render children elements. react bails to rerender children elements based solely off its parent because react by nature
// doesnt need to rerender something if its a carbon copy of that something. In this case, props children are essentiatially
// just that, PROPS. meaning that they are still the same next time react needs to determine a rerender and thus it does not
// unnecessarily rerender a thing :D
const AppWrapper1 = () => {
  return (
    <div>
      <AppProvider>
        <Child1 />
        <Child2 />
      </AppProvider>
    </div>
  );
};

// bad render
const AppWrapper2 = () => {
  const [state, setState] = React.useState(1);
  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={setState}>
        <Child1 />
        <Child2 />
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
};

function App() {
  return (
    <div className="App">
      <AppWrapper1 />
      <AppWrapper2 />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);