反应:自定义钩子不适用于上下文

时间:2020-05-25 18:47:45

标签: reactjs

我创建了一个自定义钩子,将对象存储在useState钩子中,并允许更改属性而不会丢失其他条目。

const useObject = initialValue => {
  const [state, setState] = useState(initialValue);

  return [
    state,
    newState => {
      setState({
        ...state,
        ...newState
      });
    }
  ];
};

此挂钩在我的组件中有效,但在将其分配给上下文时无效。

这是我所做的:

  1. 我创建了一个上下文:

export const navigation = createContext();

https://codesandbox.io/s/keen-glitter-3nob7?file=/src/store.js:40-83

  1. 我创建了一个useObject变量并将其作为值分配给我的上下文提供程序

<navigation.Provider value={useObject()}>

https://codesandbox.io/s/keen-glitter-3nob7?file=/src/Layout.js:234-284

  1. 我通过useContext加载上下文并更改其值

const [navigationState, setNavigationState] = useContext(navigation);

https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js:476-616

结果:

上下文始终存储新条目并删除所有现有条目。

有人知道为什么吗?

这是“沙箱”链接。您可以通过单击过滤器按钮进行测试。我希望看到{search:true, icon: 'times'}作为上下文值。谢谢!

https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js

1 个答案:

答案 0 :(得分:0)

这里要注意一件事。 App.js中的useEffect仅运行一次,因此,使用setNavigationState设置的onClick函数将在其定义的点(即初始渲染)使用其闭包中的值。

由于这个原因,当您从上下文的Header.js中调用该函数时,该值连同localState都将重置为初始值。

解决方案1:

这里的一种解决方案是使用回调方法进行状态更新。为此,您需要稍微修改useObject的实现,以提供使用setState的回调值的功能

const useObject = initialValue => {
  const [state, setState] = useState(initialValue);

  return [
    state,
    newState => {
      if(typeof newState === 'function') {
        setState((prev) => ({ ...prev, ...newState(prev)}));
      } else {
      setState({
        ...state,
        ...newState
      });
    }
    }
  ];
};

然后在onContextClick函数中使用它

const onContextClick = () => {
    setState(prevState => {
      setNavigationState(prev => ({ icon: ICON[prevState.isOpen ? 0 : 1] }));
      return { isOpen: !prevState.isOpen };
    });
  };

Working DEMO

解决方案2:

解决此问题的另一种更简单的方法是对useCallback使用onContextClick并在每次关闭状态更新时使用useEffect更新导航状态

 const onContextClick = React.useCallback(() => {
    setNavigationState({ icon: ICON[state.isOpen ? 0 : 1] });
    setState({ isOpen: !state.isOpen });
  }, [state]);

  useEffect(() => {
    setNavigationState({
      search: true,
      icon: ICON[0],
      onClick: onContextClick
    });
  }, [onContextClick]);

Working demo