React: hooks: useContext with useState not updating

Created on 26 Jan 2019  ·  3Comments  ·  Source: facebook/react

Do you want to request a feature or report a bug?

seems it's a bug. 😕

What is the current behavior?

Nested context provider and useContext hooks seems to be conflicting, updates get discarded.

What is the expected behavior?

When connecting to a context, it should update whenever it's value changes.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

  • react: 18.8.0-alpha.1 (also reproduced on 16.7.0-alpha.0)
  • browser: chrome 71
  • os: macOS Sierra

more details

While working on a cleanup of a localStorage "connection",
I tried to mix 2 articles ([1] & [2]) from the official react documentation, I've implemented it with hooks, but the value seems not to be passing through.

I've put up a streamlined demo on codesandbox [3].

The actual implementation is only a couple of lines more (parsing it from and stringifying it to JSON).

Workarounds that I found:

  • If I create a new function on each render around the setValue function, it actually works.

    • but this goes against the advice on [1] about avoiding creating new values.

  • Migrate it to a class and use componentDidUpdate instead of useEffect.

    • I'm actually using this right now, as it works. Including saving a reference to the function in the state.


Is there anything that shouldn't work on the code below? the effect gets triggered with the changes,
but the value doesn't get updated on the components that consume via hook. see repro code [3]

const createLocalStorage = key => {
    const initialValue = localStorage.getItem(key)
    const ValueContext = createContext(initialValue)
    const SetterContext = createContext(() => {})

    const useStorage = () => [ValueContext, SetterContext].map(useContext)

    const Provider = ({children}) => {
        const [value, setValue] = useState(initialValue)

        useEffect(
            () => {
                console.log('effect', value)
                localStorage.setItem(key, value)
            },
            [value],
        )

        return (
            <ValueContext.Provider value={value}>
                <SetterContext.Provider value={setValue}>
                    {children}
                </SetterContext.Provider>
            </ValueContext.Provider>
        )
    }

    return [Provider, useStorage]
}

[1]: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
[2]: https://reactjs.org/docs/context.html#updating-context-from-a-nested-component
[3]: https://codesandbox.io/s/0yzjr8vnrv


hlcecpq

Question

Most helpful comment

I've put up a streamlined demo on codesandbox [3].

It looks to me like your useContext calls are in the same component as a Provider. In that sense they're reading context above the Provider (the default one).

Provider's value only influences context for components inside of it.

By the way, if you didn't try to cheat the Hooks rules with code like [A, B].map(useContext) you might have noticed this sooner ;-)

All 3 comments

I've put up a streamlined demo on codesandbox [3].

It looks to me like your useContext calls are in the same component as a Provider. In that sense they're reading context above the Provider (the default one).

Provider's value only influences context for components inside of it.

By the way, if you didn't try to cheat the Hooks rules with code like [A, B].map(useContext) you might have noticed this sooner ;-)

hey Dan! thanks for the quick response (: just fixed it for me.

btw: useContext (via useStorage) is inside App, which _is_ inside of Provider in the ReactDOM.render function. (maybe I made the example too short to see this nesting).


solution:

I just solved it by modifying that quirky .map line: I missed the observedBits parameter, that ended up being passed on I've read about it here in the Issues/PRs, but forgot about it when implementing, and ended up messing with the behaviour.

Aaah you're right I misread your example. Yeah observedBits is the issue here. Seems like a pitfall, but then we also don't recommend to ever call useContext dynamically so..

Was this page helpful?
0 / 5 - 0 ratings