钩子更改状态不会更新上下文提供者的值?

时间:2020-05-19 09:36:50

标签: javascript reactjs context-api

我有2个组件,还有一个上下文提供程序,当我在父级别调用我的钩子时,更改状态并让这2个组件通过上下文获取值没有问题

contex api用法的工作演示,但我在父级调用了更改状态,这不是我想要的 https://stackblitz.com/edit/react-51e2ky?file=index.js

我想用钩子更改内部组件的状态,但是单击navbar login时看不到该值已更改。

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

父母:

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

自定义钩子

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

  return {
    user,
    setUser
  }
}

我可以将setUser从父级传递给子级,但是我想避免这种情况,我希望可以使用上下文api并无缝地响应钩子。

4 个答案:

答案 0 :(得分:2)

当前,您仅在上下文中设置user值,这就是为什么获取正确值的原因。

但是,在Navbar.js组件中,您正在调用loginHook,这将为该钩子创建一个新的“实例”,并有效地具有其自己的状态。

我建议您也在上下文中添加更新功能,

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

}

这样,您也可以在孩子中访问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>
}

另外,请注意:最好以use开始自定义钩子,因为这是编写自己的钩子时的最佳实践。

重要的警告是,这实际上不是一个好习惯。如果您的user要更改,则所有仅在监听setUser的组件也将得到更新,从而进行无用的重新渲染。您可以通过使用两种不同的上下文来解决此问题,一种用于值,另一种用于更新程序。您可以了解有关此here

的更多信息

答案 1 :(得分:0)

您不能从孩子那里更改父母的上下文信息,否。您需要从父母那里传递一些东西给孩子,孩子可以用来让父母知道需要更新上下文(例如setUser的父母副本)。您可以通过prop或在上下文中添加setUser来做到这一点,尽管我倾向于将其作为对需要设置用户的组件的支持,而不是上下文它们都可以访问的组件

在两个地方都不能使用loginHook的原因是每个组件(AppNavbar)都有自己的user副本。这是挂钩如何工作的基础。 (如果以某种方式使他们共享状态信息,useState根本不起作用-所有状态将在所有组件之间共享。)Dan Abramov的A Complete Guide to useEffect可能是有帮助的读物(更多关于如何钩子和组件的工作要比useEffect特有的多。)

答案 2 :(得分:0)

您必须注意,自定义钩子不共享实例引用,因此,如果您在App中使用loginHook并在Navbar中使用另一个钩子,它们将创建2个独立的状态和更新程序

现在使用自定义钩子中的setter现在将在上下文中更新状态。

您可以通过编写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
  }
}

现在有多种方法可以编写此代码,但是在上述情况下,最好的方法是根本不使用自定义钩子,因为它无论如何都没有用

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

答案 3 :(得分:-1)

Navbar.js中,使用loginHook钩子将创建一个新的单独状态,该状态不同于App.js中使用的状态。您需要编写钩子,以便使用上下文而不是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);

然后像这样使用它:

/* App.js */

import {UserProvider} from './UserContext';

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

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