Включение ловушки useContext приводит к тому, что дочернее свойство useState сбрасывается до исходного значения - PullRequest
2 голосов
/ 25 марта 2020

Я боролся с этой проблемой в течение нескольких дней, поэтому любая помощь будет принята с благодарностью. У нас есть глобальный контекст данных, который мы включаем в несколько компонентов в иерархии. Я повторил проблему, которую мы видим в приведенном ниже примере с c.

Вы также можете запустить его на 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 голосов
/ 25 марта 2020

Я думаю, что проблема заключается в следующем: при вызове doSomethingInContext срабатывает setContextValue (после тайм-аута). Когда он запускается, он обновляет данные Provider, что приводит к перестройке Routes (так как это потребитель). Перестройка Routes изменяет функцию render, в результате чего все, что находится под ним, выбрасывается и восстанавливается. Попробуйте useCallback: в Routes, добавьте это:

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

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

Таким образом, функция не изменится, и дочерние элементы должны быть сохранены в пересборке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...