Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

hooks: useContext with useState not updating #14708

Closed
leonardodino opened this issue Jan 26, 2019 · 6 comments
Closed

hooks: useContext with useState not updating #14708

leonardodino opened this issue Jan 26, 2019 · 6 comments

Comments

@leonardodino
Copy link

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

@gaearon
Copy link
Collaborator

gaearon commented Jan 27, 2019

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 ;-)

@leonardodino
Copy link
Author

leonardodino commented Jan 27, 2019

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.

@gaearon
Copy link
Collaborator

gaearon commented Jan 27, 2019

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..

@utterly-calm
Copy link

utterly-calm commented Apr 14, 2021

@gaearon @leonardodino

How to fix it. I think I'm in a similar situation.

import React, { createContext, useEffect, useState } from "react";

const AppContext = createContext({
  memberFirstName: "",
});
AppContext.displayName = "App";

function AppProvider({ children }) {
  const [memberFirstName, setMemberFirstName] = useState("");

  useEffect(() => {
    setTimeout(() => {
      setMemberFirstName("Manu");
      alert(memberFirstName);
    }, 1000);
  });
  const context = { memberFirstName };

  return <AppContext.Provider value={context}>{children}</AppContext.Provider>;
}

export { AppContext, AppProvider };

Alert is working fine after settimeout but re-rendering isn't happening. It only re-renders when I'm rendering state variable inside context i.e.

return <AppContext.Provider value={context}>{memberFirstName }{children}</AppContext.Provider>;

But I cannot do that, I need this state data after API call to be availble via context for other components.

@utterly-calm
Copy link

If I have to get data from API then it has to be called before <Provider value={context from useEffect}, how else can I handle it?

@utterly-calm
Copy link

I was assuming, I'm wrapping it correctly, but suddenly I realized and checked and found that I wrapped it in wrong container.. My bad.. I'm good here... thanks..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants