Приложение React зависает из-за хука useEffect в версии 16.9 при нормальной работе в версии 16,8

Код отлично работает на React 16.8, но зависает на> = 16.9. Я посмотрел на изменения для 16.9, но все, что было упомянуто, было дополнительным предупреждением о бесконечном цикле, а не фактическим изменением поведения, так что этого не должно происходить, но оно есть.

Установка зависимости useEffect на [] действительно прерывает цикл, но означает, что код необходимо изменить, и я не уверен, как это сделать.

Приведенный ниже код также можно найти в изолированной программной среде здесь. Версия установлена ​​на 16.8.4, поэтому она не зависает.

index.jsx

//...
import { SessionContext, getSessionCookie, setSessionCookie } from "./session";
import "./styles.css";

const history = createBrowserHistory();

const LoginHandler = ({ history }) => {
  const [email, setEmail] = useState("");
  const [loading, setLoading] = useState(false);
  const handleSubmit = async e => {
    e.preventDefault();
    setLoading(true);
    // NOTE request to api login here instead of this fake promise
    await new Promise(r => setTimeout(r(), 1000));
    setSessionCookie({ email });
    history.push("/");
    setLoading(false);
  };

  if (loading) {
    return <h4>Logging in...</h4>;
  }

  return (
    //...
  );
};

const LogoutHandler = ({ history }: any) => {
  //...
};

const ProtectedHandler = ({ history }) => {
   //...
};

const Routes = () => {
  const [session, setSession] = useState(getSessionCookie());
  useEffect(
    () => {
      setSession(getSessionCookie());
    },
    [session]
  );

  return (
    <SessionContext.Provider value={session}>
      <Router history={history}>
        <div className="navbar">
          <h6 style={{ display: "inline" }}>Nav Bar</h6>
          <h6 style={{ display: "inline", marginLeft: "5rem" }}>
            {session.email || "No user is logged in"}
          </h6>
        </div>
        <Switch>
          <Route path="/login" component={LoginHandler} />
          <Route path="/logout" component={LogoutHandler} />
          <Route path="*" component={ProtectedHandler} />
        </Switch>
      </Router>
    </SessionContext.Provider>
  );
};

const App = () => (
  <div className="App">
    <Routes />
  </div>
);

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

session.ts

import React from "react";
import * as Cookies from "js-cookie";

export const setSessionCookie = (session: any): void => {
  Cookies.remove("session");
  Cookies.set("session", session, { expires: 14 });
};

export const getSessionCookie: any = () => {
  const sessionCookie = Cookies.get("session");

  if (sessionCookie === undefined) {
    return {};
  } else {
    return JSON.parse(sessionCookie);
  }
};

export const SessionContext = React.createContext(getSessionCookie());

Ожидаемый результат: в 16.9 приложение работает так же, как и в 16.8

Фактический результат: приложение зависает в версии 16.9 из-за предполагаемого бесконечного цикла использования.

Сообщение об ошибке: Предупреждение: превышена максимальная глубина обновления. Это может произойти, когда компонент вызывает setState внутри useEffect, но useEffect либо не имеет массива зависимостей, либо одна из зависимостей изменяется при каждом рендеринге.


person JH-    schedule 11.11.2019    source источник


Ответы (1)


JSON.parse(sessionCookie) всегда возвращает новый объект.

setSession(getSessionCookie()) устанавливает сеанс для этого нового объекта

[session] говорит, что запускается всякий раз, когда новый объект сеанса устанавливается в состояние (что происходит при каждом рендеринге).

Итак, вы видите бесконечный цикл. Чтобы остановить это, вы можете сделать что-то вроде:

useEffect(
    () => {
      const newSessionCookie = getSessionCookie()
      if(newSessionCookie.uniqueId !== session.uniqueId) {
        setSession(getSessionCookie());
      }
    },
    [session]
  )

Вам просто нужно убедиться, что у вас есть уникальный идентификатор для файла cookie.

person Jake Luby    schedule 11.11.2019
comment
Вы также можете отслеживать опору в массиве зависимостей. например [session.email]. Каждый раз, когда сессионное электронное письмо (или любая другая опора, которую вы выберете) изменяется, запускайте этот эффект повторно. Это избавляет от необходимости читать cookie сеанса и оператор if. - person Dennis Martinez; 11.11.2019
comment
Это сработает, если вы используете только session.email. Поскольку в приведенном выше коде используется весь объект сеанса, вам необходимо определить весь объект сеанса как зависимость. - person Jake Luby; 11.11.2019
comment
Я не вижу, где внутри эффекта используется весь объект сеанса? Это следует за тем же, что вы делаете, проверяя уникальный идентификатор и устанавливая некоторый сеанс, если он изменился. Вам не нужно отслеживать весь объект в массиве зависимостей. - person Dennis Martinez; 11.11.2019
comment
Ага, ты прав. Однако, если у вас нет никакой логики внутри эффекта, фактически использующего значения session или session.email, вы должны получить ненужную ошибку линтинга зависимостей. - person Jake Luby; 11.11.2019
comment
Вы не должны использовать useEffect. Однако вы получите это предупреждение при использовании таких методов, как useCallback или useMemo. codeandbox.io/s/serverless-violet-nrblf - person Dennis Martinez; 11.11.2019
comment
Спасибо! К сожалению, это создало еще одну проблему; по какой-то причине сеанс не устанавливается должным образом, если я вручную не перейду на другой URL-адрес. Т.е. history.push не работает, потому что при попытке доступа к информации о сеансе через контекст он появляется как пустой объект. const ProtectedHandler = ({history}) = ›{const session = useContext (SessionContext); // ...} Сессия принимается правильно только в том случае, если я жестко перехожу на страницу вручную. - person JH-; 11.11.2019