Andre Thonas
Andre Thonas

Reputation: 311

hook change state doesn't update context provider's value?

I have 2 component, and a context provider, when I call my hook at parent level, I have no issue changing the state and having those 2 component getting the value via context

working demo of contex api usage but I call change state at parent level which is not what I wanted https://stackblitz.com/edit/react-51e2ky?file=index.js

I want to change state at inner component with hook, but I don't see the value been changed when I click on the navbar login.

https://stackblitz.com/edit/react-rgenmi?file=Navbar.js

parent:

const App = () => {
  const {user} = loginHook()
    return (
      <UserContext.Provider value={user}>
        <Navbar />
        <Content />
      </UserContext.Provider>
    );

}

Navbar.js

const Navbar = () => {
  const user = React.useContext(userContex)
  const {setUser} = loginHook()

  return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
    setUser({
      name: 'jane'
    })
  }}>navbar Login</button>}</div>
}

custom hook

const loginHook = () => {
  const [user, setUser] = React.useState(null)

  return {
    user,
    setUser
  }
}

I can pass setUser from parent to children but I want to avoid that, I expect I can use context api and react hook seamlessly.

Upvotes: 5

Views: 4186

Answers (4)

JDansercoer
JDansercoer

Reputation: 517

Currently, you're only setting the user value in the context, which is why getting the correct value will work.

However, in your Navbar.js component, you are making a call to loginHook, which will create a new "instance" of that hook, effectively having its own state.

I suggest you add the update function in your context as well, as such

const App = () => {
  const {user, setUser} = loginHook()
    return (
      <UserContext.Provider value={{ user, setUser}}>
        <Navbar />
        <Content />
      </UserContext.Provider>
    );

}

That way you can access the setUser in your children as well, e.g.

const Navbar = () => {
  const {user, setUser} = React.useContext(userContex)

  return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
    setUser({
      name: 'jane'
    })
  }}>navbar Login</button>}</div>
}

Also, small note: it's best to start you custom hook with use, as that's a best-practice when writing your own hooks.

Important caveat however, this is not really a good practice. If your user were to change, all components that are only listening to setUser will also get an update an thus do a useless rerender. You can solve this by using two different contexts, one for the value, and one for the updater. You can read more about this here

Upvotes: 4

Shubham Khatri
Shubham Khatri

Reputation: 281686

You must note that custom hooks do not share instance references, so if you use the loginHook in App and another one in Navbar, they will create 2 separate states and updaters

Now using a setter from custom hook will now update the state in context.

You can restructure this by writing your loginHook so that it internally uses context and then using it

const App = () => {
  const [user, setUser] = useState();
    return (
      <UserContext.Provider value={{user, setUser}}>
        <Navbar />
        <Content />
      </UserContext.Provider>
    );

}

const Navbar = () => {
  const {user, setUser} = loginHook();

  return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
    setUser({
      name: 'jane'
    })
  }}>navbar Login</button>}</div>
}
const loginHook = () => {
  const {user, setUser} = React.useContext(UserContext)

  return {
    user,
    setUser
  }
}

Now there are multiple ways to write this code, However the best way in the above scenario is not use a custom hook at all since it anyway is not useful

const App = () => {
  const [user, setUser] = useState();
    return (
      <UserContext.Provider value={{user, setUser}}>
        <Navbar />
        <Content />
      </UserContext.Provider>
    );

}

const Navbar = () => {
  const {user, setUser} = React.useContext(UserContext);

  return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
    setUser({
      name: 'jane'
    })
  }}>navbar Login</button>}</div>
}

Upvotes: 0

trixn
trixn

Reputation: 16309

In your Navbar.js you use your loginHook hook which will create a new separate state that is different from the state used in your App.js. You need to write your hook so that is uses the context instead of useState:

/* UserContext.js */

const UserContext = createContext();

export const UserProvider = ({children}) => {
    const [user, setUser] = useState(null);

    return (
        <UserContext.Provider value={{user, setUser}}>
            {children}
        </UserContext.Provider>
    );
}

export const useLogin = () => useContext(UserContext);

Then use it like that:

/* App.js */

import {UserProvider} from './UserContext';

const App = () => (
    <UserProvider>
        <Navbar />
        <Content />
    </UserProvider>
);

and

/* Navbar.js */

import {useLogin} from './UserContext';

const Navbar = () => {
  const {user, setUser} = useLogin();

  return <div>{user ? <span>{user.name}</span> : <button onClick={() => {
    setUser({
      name: 'jane'
    })
  }}>navbar Login</button>}</div>
}

Upvotes: -1

T.J. Crowder
T.J. Crowder

Reputation: 1074266

You cannot change the parent's context information from the child, no. You'll need to pass something to the child from the parent that the child can use to let the parent know that the context needs to be updated (such as the parent's copy of setUser). You can do that via a prop or by adding setUser to the context, though I'd lean toward just doing it as a prop to components that need to be able to set the user, rather than context they all have access to.

The reason using loginHook in both places didn't work is that each component (App and Navbar) has its own copy of user. This is fundamental to how hooks work. (If it somehow made them share the state information, useState wouldn't work at all — all state would be shared across all components.) Dan Abramov's A Complete Guide to useEffect may be a helpful read (it's more about how hooks and components work than it is specifically about useEffect).

Upvotes: 0

Related Questions