2

I was playing around with React Context API and found that the provider renders twice at each value change.

Here is what I've done.

  1. Created a context, then a component which renders its provider.
  2. The provider provides a state value and its setter.
  3. Created a direct child that renders directly its children.
  4. Created a consumer which reads the context value and adds a button to change it.
  5. Each component executes a console.log when it renders.
  6. Added an effect to the provider without deps to log every time it renders.
  7. Added a ref in the provider that is incremented at each render and it is logged in both the render and the effect.

The problem ?

Each time I hit the button to change the context value, the provider renders twice, but the effect is executed only once.

So the ref is incremented always twice, but the effect only gets the last value each time (it skips a value!).

Also, at the first render of the provider it logs twice the same ref value, which is very strange to me.

Can anybody tell me why I am getting this behavior ?

Here is the code:

The provider:

const MyContext = React.createContext(0);

function Provider({ children }) {
  const state = React.useState("Yes");
  const ref = React.useRef(0);
  ref.current += 1;
  console.log("Context provider ", ref.current);
  React.useEffect(() => {
    console.log("effect on provider, ref value = ", ref.current);
  });

  return <MyContext.Provider value={state}>{children}</MyContext.Provider>;
}

The two children

function DirectChild({ children }) {
  console.log("provider direct child");
  return children;
}

function Consumer() {
  console.log("consumer");
  const [state, setState] = React.useContext(MyContext);
  return (
    <div>
      <span>State value: {state}</span>
      <br />
      <button onClick={() => setState(old => old + "Yes")}>Change</button>
    </div>
  );
}

The App

export default function App() {
  console.log("APP");
  return (
    <Provider>
      <DirectChild>
        <Consumer />
      </DirectChild>
    </Provider>
  );
}

Here is a codesandbox demo

Incepter
  • 2,711
  • 15
  • 33
  • Moved the state logic to app level, and the behavior is now in `App` too, rendering twice, logging twice, and only one effect , here is [a demo](https://codesandbox.io/s/angry-hill-5p73e) – Incepter Apr 30 '20 at 21:18
  • It was my understanding that refs are meant for references to Dom objects, not state refs (that's `useState`). I suppose you could use them to ref anything though, just not sure that's their intent: ["avoid using refs for anything that could be done declaratively"](https://reactjs.org/docs/refs-and-the-dom.html). – Garrett Motzner Apr 30 '20 at 21:28
  • Yes, refs are meant to create a mutable object that you can mutate at your own will. I found the reason behind this, I will post the answer right now; It is React.strictMode in codesandbox – Incepter Apr 30 '20 at 21:30
  • And only affects development mode – Incepter Apr 30 '20 at 21:30
  • Yep, I suspected that was the case :) I answered a similar question last week or so... – Garrett Motzner Apr 30 '20 at 21:31
  • Saw the duplicate flag and accepted it, although I had already posted my own answer. Thanks for your help – Incepter Apr 30 '20 at 21:38

1 Answers1

6

Found the reason,

There is no bug, and no strange behavior, I was just missing that codesandbox by default renders the App within a React.StrictMode parent.

First, I ported the code to my local project, then observed that there was no issues.

Searched over codesandbox repo issues and found that it is linked to a react behavior on strict mode:

It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.

Codesandbox issue

React issue

But still, The effect is executed only once and misses my ref update.

EDIT

React 18 introduced strict effects, which runs effects also twice under strict mode in development.

Incepter
  • 2,711
  • 15
  • 33
  • If you want to track ref value you can use `React.useCallback` Here's the example ` const ref = React.useRef(0); let setRef = React.useCallback(setValue => { ref.current = setValue(ref.current); console.log(ref.current) }, []) React.useEffect(() => { setRef(old => old + 1) }, [state[0]]);` – Józef Podlecki Apr 30 '20 at 22:00