Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React context with hooks prevent re render

I use React context with hooks as a state manager for my React app. Every time the value changes in the store, all the components re-render.

Is there any way to prevent React component to re-render?

Store config:

import React, { useReducer } from "react";
import rootReducer from "./reducers/rootReducer";

export const ApiContext = React.createContext();

export const Provider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, {});

  return (
    <ApiContext.Provider value={{ ...state, dispatch }}>
      {children}
    </ApiContext.Provider>
  );
};

An example of a reducer:

import * as types from "./../actionTypes";

const initialState = {
  fetchedBooks: null
};

const bookReducer = (state = initialState, action) => {
  switch (action.type) {
    case types.GET_BOOKS:
      return { ...state, fetchedBooks: action.payload };

    default:
      return state;
  }
};

export default bookReducer;

Root reducer, that can combine as many reducers, as possible:

import userReducer from "./userReducer";
import bookReducer from "./bookReducer";

const rootReducer = ({ users, books }, action) => ({
  users: userReducer(users, action),
  books: bookReducer(books, action)
});

An example of an action:

import * as types from "../actionTypes";

export const getBooks = async dispatch => {
  const response = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
    method: "GET"
  });

  const payload = await response.json();

  dispatch({
    type: types.GET_BOOKS,
    payload
  });
};
export default rootReducer;

And here's the book component:

import React, { useContext, useEffect } from "react";
import { ApiContext } from "../../store/StoreProvider";
import { getBooks } from "../../store/actions/bookActions";

const Books = () => {
  const { dispatch, books } = useContext(ApiContext);
  const contextValue = useContext(ApiContext);

  useEffect(() => {
    setTimeout(() => {
      getBooks(dispatch);
    }, 1000);
  }, [dispatch]);

  console.log(contextValue);

  return (
    <ApiContext.Consumer>
      {value =>
        value.books ? (
          <div>
            {value.books &&
              value.books.fetchedBooks &&
              value.books.fetchedBooks.title}
          </div>
        ) : (
          <div>Loading...</div>
        )
      }
    </ApiContext.Consumer>
  );
};

export default Books;

When the value changes in Books component, another my component Users re-renders:

import React, { useContext, useEffect } from "react";
import { ApiContext } from "../../store/StoreProvider";
import { getUsers } from "../../store/actions/userActions";

const Users = () => {
  const { dispatch, users } = useContext(ApiContext);
  const contextValue = useContext(ApiContext);

  useEffect(() => {
    getUsers(true, dispatch);
  }, [dispatch]);

  console.log(contextValue, "Value from store");

  return <div>Users</div>;
};

export default Users;

What's the best way to optimize context re-renders? Thanks in advance!

like image 466
lecham Avatar asked Jul 14 '19 18:07

lecham


People also ask

How do you avoid re-rendering in React using hooks?

1. Memoization using useMemo() and UseCallback() Hooks. Memoization enables your code to re-render components only if there's a change in the props. With this technique, developers can avoid unnecessary renderings and reduce the computational load in applications.

How do I prevent context from Rerendering?

This is the core principal of Context API, when a context value changed all components re-render. In order to prevent this we can use memo which will skip unnecessary re-renders of that component. import { memo } from "react"; const MidChild = memo(() => { console.

Does context change cause re-render?

🧐 Re-renders reason: context changesWhen the value in Context Provider changes, all components that use this Context will re-render, even if they don't use the changed portion of the data directly.

Does useMemo trigger re-render?

With useMemo() , we can return memoized values and avoid re-rendering if the dependencies to a function have not changed.


1 Answers

Books and Users currently re-render on every cycle - not only in case of store value changes.

1. Prop and state changes

React re-renders the whole sub component tree starting with the component as root, where a change in props or state has happened. You change parent state by getUsers, so Books and Users re-render.

const App = () => {
  const [state, dispatch] = React.useReducer(
    state => ({
      count: state.count + 1
    }),
    { count: 0 }
  );

  return (
    <div>
      <Child />
      <button onClick={dispatch}>Increment</button>
      <p>
        Click the button! Child will be re-rendered on every state change, while
        not receiving any props (see console.log).
      </p>
    </div>
  );
}

const Child = () => {
  console.log("render Child");
  return "Hello Child ";
};


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

Optimization technique

Use React.memo to prevent a re-render of a comp, if its own props haven't actually changed.

// prevents Child re-render, when the button in above snippet is clicked
const Child = React.memo(() => {
  return "Hello Child ";
});
// equivalent to `PureComponent` or custom `shouldComponentUpdate` of class comps

Important: React.memo only checks prop changes (useContext value changes trigger re-render)!


2. Context changes

All context consumers (useContext) are automatically re-rendered, when the context value changes.

// here object reference is always a new object literal = re-render every cycle
<ApiContext.Provider value={{ ...state, dispatch }}>
  {children}
</ApiContext.Provider>

Optimization technique

Make sure to have stable object references for the context value, e.g. by useMemo Hook.

const [state, dispatch] = useReducer(rootReducer, {});
const store = React.useMemo(() => ({ state, dispatch }), [state])

<ApiContext.Provider value={store}>
  {children}
</ApiContext.Provider>

Other

Not sure, why you put all these constructs together in Books, just use one useContext:

const { dispatch, books } = useContext(ApiContext);
// drop these
const contextValue = useContext(ApiContext); 
<ApiContext.Consumer> /* ... */ </ApiContext.Consumer>; 

You also can have a look at this code example using both React.memo and useContext.

like image 72
ford04 Avatar answered Sep 29 '22 20:09

ford04