简体   繁体   English

挂钩更改 state 不会更新上下文提供程序的值?

[英]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我有 2 个组件和一个上下文提供程序,当我在父级别调用我的钩子时,更改 state 并让这 2 个组件通过上下文获取值没有问题

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 contex api 使用的工作演示,但我在父级调用更改 state 这不是我想要的 https://stack2blitz.com/edit/jsfile?

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 .我想用钩子更改内部组件的 state ,但是当我单击navbar login时,我没有看到值被更改。

https://stackblitz.com/edit/react-rgenmi?file=Navbar.js 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导航栏.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.我可以将 setUser 从父级传递给子级,但我想避免这种情况,我希望我可以使用上下文 api 并无缝响应钩子。

Currently, you're only setting the user value in the context, which is why getting the correct value will work.目前,您只是在上下文中设置user值,这就是为什么获得正确的值会起作用。

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.但是,在您的Navbar.js组件中,您正在调用loginHook ,这将创建该挂钩的新“实例”,有效地拥有自己的 state。

I suggest you add the update function in your context as well, as such我建议您在您的上下文中添加更新 function ,因此

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, eg这样你也可以在你的孩子中访问setUser ,例如

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.另外,请注意:最好use开始自定义钩子,因为这是编写自己的钩子时的最佳实践。

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.如果您的user要更改,所有仅侦听setUser的组件也将获得更新,从而进行无用的重新渲染。 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您可以在此处阅读有关此内容的更多信息

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 ).您需要从父级传递一些东西给子级,子级可以使用这些内容让父级知道需要更新上下文(例如父级的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.您可以通过道具或通过将setUser添加到上下文来做到这一点,尽管我倾向于将其作为需要能够设置用户的组件的道具,而不是他们都可以访问的上下文。

The reason using loginHook in both places didn't work is that each component ( App and Navbar ) has its own copy of user .在两个地方都使用loginHook的原因是每个组件( AppNavbar )都有自己的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 ). (如果它以某种方式让他们共享 state 信息, useState将根本不起作用 - 所有 state 将在所有组件之间共享。)Dan Abramov's A Complete Guide to useEffect可能会有所帮助(更多关于钩子和组件如何工作而不是专门关于useEffect )。

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您必须注意自定义挂钩不共享实例引用,因此如果您在 App 中使用 loginHook 而在 Navbar 中使用另一个,它们将创建 2 个独立的状态和更新程序

Now using a setter from custom hook will now update the state in context.现在使用自定义钩子中的设置器现在将在上下文中更新 state。

You can restructure this by writing your loginHook so that it internally uses context and then using it您可以通过编写 loginHook 来重构它,以便它在内部使用上下文,然后使用它

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

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 .在您的Navbar.js ,您使用loginHook钩子,它将创建一个新的单独的 state,它与您 App.js 中使用的App.js不同。 You need to write your hook so that is uses the context instead of useState :您需要编写您的钩子,以便使用上下文而不是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>
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM