包括useContext挂钩会导致子项的useState重置为初始值

时间:2020-03-25 02:12:04

标签: reactjs react-router react-hooks react-router-dom

我已经为这个问题苦苦挣扎了几天,所以任何帮助将不胜感激。我们有一个全局数据上下文,该上下文包含在层次结构中的一些组件中。我已经复制了以下基本示例中遇到的问题。

您也可以run it on CodeSandbox

问题在于,childValue组件中的Content每次重新渲染时都会重置为其初始useState值。但这只是 的情况,其中useData上下文包含在Routes组件的链中。删除useData行(并对isAuthenticated进行硬编码)可以解决此问题。但是,这不是可接受的解决方案,因为我们需要能够将某些值保留在全局上下文中,并在任何地方包含它们。

我尝试将React.memo(...)中的内容包装起来毫无用处。我在这里想念什么?

import React, { useState, useContext, useEffect } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { render } from "react-dom";

// Routes

const Routes = () => {
  // We see desired behavior if useData() is removed here.
  //  i.e. Initial Value does NOT get reset in Content
  const { isAuthenticated } = useData();
  // const isAuthenticated = true // uncomment this after removing the above line

  const RouteComponent = isAuthenticated ? PrivateRoute : Route;

  return (
    <Switch>
      <RouteComponent path="/" render={props => <Content {...props} />} />
    </Switch>
  );
};

const PrivateRoute = ({ render: Render, path, ...rest }) => (
  <Route
    path={path}
    render={props => <Render isPrivate={true} {...props} />}
    {...rest}
  />
);

// Data Context

export const DataContext = React.createContext();
export const useData = () => useContext(DataContext);
export const DataProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [contextValue, setContextValue] = useState(false);

  useEffect(() => {
    setIsAuthenticated(true);
  }, []);

  const doSomethingInContext = () => {
    setTimeout(() => setContextValue(!contextValue), 1000);
  };

  return (
    <DataContext.Provider
      value={{
        isAuthenticated,
        doSomethingInContext,
        contextValue
      }}
    >
      {children}
    </DataContext.Provider>
  );
};

// Page Content

const Content = props => {
  const { contextValue, doSomethingInContext } = useData();

  const [childValue, setChildValue] = useState("Initial Value");

  useEffect(() => {
    if (childValue === "Value set on Click") {
      doSomethingInContext();
      setChildValue("Value set in useEffect");
    }
  }, [childValue]);

  return (
    <div>
      <div style={{ fontFamily: "monospace" }}>contextValue:</div>
      <div>{contextValue.toString()}</div>
      <br />
      <div style={{ fontFamily: "monospace" }}>childValue:</div>
      <div>{childValue}</div>
      <br />
      <button onClick={() => setChildValue("Value set on Click")}>
        Set Child Value
      </button>
    </div>
  );
};

const App = () => {
  return (
    <DataProvider>
      <Router>
        <Routes />
      </Router>
    </DataProvider>
  );
};

render(<App />, document.getElementById("root"));

1 个答案:

答案 0 :(得分:0)

我认为问题是这样的:当您调用doSomethingInContext时,它会触发setContextValue(在超时后)。在运行时,它会更新Provider的数据,这会导致Routes重建(因为它是使用者)。重建Routes会更改render函数,导致下面的所有内容都被丢弃并重建。尝试useCallback:在Routes中,添加以下内容:

// In the body...
const render = useCallback(
  props => <Content {...props} />,
  []
);

// In the RouteComponent
<RouteComponent path="/" render={render} />

那样,功能不会改变,并且子项应保留在重建中。