React context with hooks prevent re render

I believe what is happening here is expected behavior. The reason it renders twice is because you are automatically grabbing a new book/user when you visit the book or user page respectively.

This happens because the page loads, then useEffect kicks off and grabs a book or user, then the page needs to re-render in order to put the newly grabbed book or user into the DOM.

I have modified your CodePen in order to show that this is the case.. If you disable 'autoload' on the book or user page (I added a button for this), then browse off that page, then browse back to that page, you will see it only renders once.

I have also added a button which allows you to grab a new book or user on demand... this is to show how only the page which you are on gets re-rendered.

All in all, this is expected behavior, to my knowledge.

Edit react-state-manager-hooks-context


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.