-1

Original Question

I am trying to learn more about the inner workings of React, and here specifically Context Providers so I don't have a use-case in mind, just trying to understand why it works the way it does.

In my context.tsx I wondering why the logged state.value is the static initial value and does not reflect the current value saved therein (see comments in the file below)

import React, { createContext, useState } from "react"; interface myState { value: number; setValue: Function; } export const MyContext = createContext<myState | null>(null); export const MyContextProvider = ({ children, }: { children: React.ReactNode; }) => { const setValue = (newVal: number) => { console.log(state.value); // why is this static? setState((prevState) => { // prevState does contain the value it currently holds return { ...prevState, value: newVal, }; }); }; const [state, setState] = useState({ value: 10, // whatever is entered here is what will be logged above setValue: setValue, }); return <MyContext.Provider value={state}>{children}</MyContext.Provider>; }; 

Here's a CodeSandbox with the above file in an app: https://codesandbox.io/p/sandbox/inspiring-pike-9972nf

EDIT

Thanks to https://stackoverflow.com/a/79437349/5086312 I've seen that changing the return value to

 return ( <MyContext.Provider value={{ ...state, // if this line is commented out the console log above // will show the initial value, else it shows the current setValue: setValue, }} > {children} </MyContext.Provider> ); 

Will produce the expected output, however commenting out the indicated line breaks the behaviour. This almost feels like some sort of compiler bug. Notice that having state.value is not actually needed inside the value={...}.

3
  • Simple, don't set state directly as the context value. Use const [value, setValue] = useState(10) and value={{ value, setValue }}
    – Phil
    CommentedFeb 14 at 5:56
  • Yet another question without a problem but just logging the wrong thing. Why are you trying to log state anyway? Was there a problem you were trying to debug or solve?
    – Phil
    CommentedFeb 14 at 6:08
  • Arguably I maybe I should have made a new question after my edit, but in any case, like I said in the first line of my question, I asked it to understand how it works "under the hood" without a use-case in mind. My question has evolved to asking why value={{...state}} and value={{...state, setValue: setValue,}} cause state.value to be different in the console log
    – culicidae
    CommentedFeb 14 at 7:40

2 Answers 2

1

See setValue is being reassigned on every rerender of MyContextProvider, there is no doubt about that.

But, are you every changing/updating the value of setState in your App component and MyContextProvider? No.

So basically you are using the same version of setValue as setState throughout the app lifecycle. In MyContextProvider, setValue has closed over setState and state, that is why it is using the old value of state everytime it runs. While it might be using the old value of setState too, that does not matter.

This is the Sandbox with a fix. It works because now what we are passing down in context is not the original version of setValue but the one that is created on every re-render. (The rerender happens because context is updated)

Based on the OP's edit: The callback inside setState is a pure function. It takes an input and returns an output, and is not closing over any value.

(prevState) => { console.log({ prevState }); // prevState does contain the value it currently holds return { ...prevState, value: newVal, }; } 

It's like a function like this:

(a,b) => { return a+b; } 

You won't expect the above to work incorrectly even if it is stale right.

So basically, yes similar to state, setState is also outdated above. But it will still function correctly because it is never relying on any outside value.


Here are some helpful resources:

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
  2. When to use functional setState
12
  • Maybe I'm misunderstanding your answer, but the setState used within setValue does update state correctly (in the example app I linked that value is displayed in an <h1> tag and incremented via the onClick of a button. So setValue has access to setState as intended (and from within the setState you can even read the actual current value. I'm trying to understand why it cannot be read outside of setState.
    – culicidae
    CommentedFeb 13 at 18:09
  • I've looked at the sandbox you linked in more detail and it's raised more questions than answers for me. I will edit my original question to reflect this.
    – culicidae
    CommentedFeb 13 at 19:17
  • Let me edit my answer. setValue need not have the most update value of setState, it is a pure function. It takes an input and returns an output based on that input. It is not closing over any other variable as such.CommentedFeb 14 at 5:45
  • "This is the Sandbox with a fix."... that should be in the answer. If CodeSandbox goes down, this answer lacks crucial detail
    – Phil
    CommentedFeb 14 at 6:02
  • 2
    See, setValue: setValue will give you the latest version because of the reasoning I gave above. Now if you comment that bit, then the state you are passing down is still the old one, where setValue is never updated. You do have one setState in your setValue but that updates only the value someResult and the function setValue is still the same from the very first render.CommentedFeb 14 at 9:21
-1

The issue lies here in the below statement.

 const [state, setState] = useState({ value: 10, setValue: setValue, }); 

Please recall the initial value passed into useState will be referenced only in the initial render. And thus, in the initial render the state state will become the given initial value. This would be sufficient in most of the cases.

But this initial value will certainly be failed if we reference a state in the closure associated with a function object. Because state will change in every render, but the state in the closure to setValue will remain the old one, the stale one. This is why the reference state.someResult always prints its initial value, although the state is changing in every render.

Solution:

In the scenario we have discussed above, we need to redefine the setValue on each render as you have rightly found as its solution.

As an alternative, we do not reference a state in a closure . As you might have noted, the updater function in React has an argument which will always result the latest state value. Since it is an argument to the function and Not resolved through a closure, it will be free from failure in this context as well.

Therefore please reference the argument instead of the stale closure as shown below.

const setValue = (newVal: number) => { setState((prevState) => { console.log(prevState.value); // this will give the latest value return { ...prevState, value: newVal, }; }); }; 
6
  • 1) Why deviate from OP's value property to someResult? I see you do this all the time where your code solutions differ subtly to the question code which makes me think you're just generating your answers 2) What did this answer add that wasn't already covered by the other answer that is ~9 hours older than this one?
    – Phil
    CommentedFeb 14 at 6:54
  • Sorry @Phil, I am extremely grateful to you, for your concern on sharpening my post. I really obliged to you. Honestly, I am very weak in writing, I used to make mistakes, I wish I would come better. With all your support, I shall keep trying. Thank you for your sincere and valuable efforts.CommentedFeb 14 at 7:11
  • 1) I suspect @WeDoTheBest4You changed that because I made an edit to the sandbox using the answer I got previously. I renamed to to someResult in there in case just calling it value in there caused confusion.
    – culicidae
    CommentedFeb 14 at 7:41
  • @culicidae, Thank you for the clarifications.CommentedFeb 14 at 8:13
  • 1
    @WeDoTheBest4You 90% of your answers look like gen-ai. They're too long, overly detailed and make the same hallucinations / mistakes that LLMs make.
    – Phil
    CommentedFeb 14 at 8:30

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.