如何停止useEffect使组件无限地重新渲染?

时间:2020-06-25 10:46:11

标签: reactjs react-hooks use-effect

再次编辑以解释===比较对象(Why are two identical objects not equal to each other?)的内存位置,这就是useEffect()不断运行的原因……我想我拥有它现在进行了排序,但是如果我说的是绝对的胡言乱语,并且有人想纠正我,将在这里暂时没有答案,这会结束:)

---第二次编辑结束----

我已经编辑了Routes常量,以将其他sessions记录到控制台。如您所见,它们是相等的,但由于某种原因,计算机似乎并不这么认为。有谁知道为什么吗?

edited Routes (to show the values)
const Routes = () => {
  const [session, setSession] = useState(getSessionCookie());
  console.log('Route session before: ', session);


  useEffect(
    () => {
      //setSession(getSessionCookie());
      const stateCookie = session;
      const getCookie = getSessionCookie();
      console.log('stateCookie: ', stateCookie);
      console.log('getCookie: ', getCookie);
      console.log('Are equal? ', stateCookie === getCookie);
    },
    [session]
  );
  console.log('Route session after: ', session);

Console log

-----编辑结束-------

我在网上找到了一个tutorial来协助我管理用户会话,但是除非重新使用useEffect依赖项,否则我在重新呈现组件方面一直遇到问题。我已经记录了session变量的值和类型,但是它没有变化,所以我不明白为什么重新渲染一直在发生。任何帮助将不胜感激。

index.js

import React, { useEffect, useState, useContext } from 'react';
import { render } from "react-dom";
import { Router, Switch, Route } from "react-router";
import { Link } from "react-router-dom";
import { createBrowserHistory } from "history";
import Cookies from "js-cookie";
import { SessionContext, getSessionCookie, setSessionCookie } from "./session";

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 (
    <div style={{ marginTop: "1rem" }}>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="Enter email address"
          value={email}
          onChange={e => setEmail(e.target.value)}
        />
        <input type="submit" value="Login" />
      </form>
    </div>
  );
};

const ProtectedHandler = ({ history }) => {
  const session = useContext(SessionContext);
  if (session.email === undefined) {
    history.push("/login");
  }
  return (
    <div>
      <h6>Protected data for {session.email}</h6>
      <Link to="/logout">Logout here</Link>
    </div>
  );
};

const LogoutHandler = ({ history }) => {
  useEffect(
    () => {
      Cookies.remove("session");
      history.push("/login");
    },
    [history]
  );

  return <div>Logging out!</div>;
};

const Routes = () => {
  const [session, setSession] = useState(getSessionCookie());
  console.log('Routes session before: ', session);
  console.log('Routes session before typeof: ', typeof session);
  useEffect(
    () => {
      setSession(getSessionCookie());
    },
    [session] // <-------------- this is the dependency that seems to be causing the trouble
  );
  console.log('Routes session: ', session);
  console.log('Routes session typeof: ', typeof 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.js

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

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

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

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

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

2 个答案:

答案 0 :(得分:1)

在使用useEffect时这是一个非常常见的问题,您不应该在依赖项数组中有一个对象,因为Object仅引用其引用,而不是实际值。创建会话时,即使您的首选属性值相同,它也会有一个新引用=>这就是为什么它会创建无限循环的原因。

如果将session用作依赖项,则应该显式比较属性值,例如session.value

我没有使用太多上下文API,但是我想您的<Routes />组件中可能有问题,<Routes />中的会话可能不需要更新,因为它只是作为提供者而已角色。通常,这是您分配初始上下文值的地方。

用户成功登录后,您可以在<LoginHandler />内更新会话。消耗上下文值的其他子组件仅需要使用useContext来获取最新的会话值。

因此,基本上您的应用程序可能看起来像这样:

// sessionContext.js
const SessionContext = React.createContext({
  session: {},
  setSession: () => {},
});

// components/routes.js
const Routes = () => {
  const [session, setSession] = useState({})
  const contextSession = {
    session,
    setSession
  }

  return (
    <SessionContext.Provider value={contextSession}>
      {children}
    </SessionContext.Provider>
  )
}

// components/childComponent.js
const ChildComponent = () => {
  const { session } = useContext(SessionContext)

  if (!session)
    return null;
  
  return <div>Logged-in</div>
}

对于复杂的状态管理,我建议您看一下redux,您不需要像上面的示例那样使用上下文。

答案 1 :(得分:0)

鉴于您仅应在更新/更改会话时才记录会话,因此应在更新状态之前进行比较,否则,由于您不断更新状态,将导致无限循环。

useEffect(() => {
  // assuming that session is not an array or object
  if (getSessionCookie().email === session.email) {
    return;
  }
  setSession(getSessionCookie());
}, [session]);

同样,在LogoutHandler组件上,您不应将history对象作为依赖项数组的一部分进行更新。实际上,不需要调用history.push(),因为在呈现组件时您应该已经在该路由中。您只应该删除一次cookie,这样就可以在安装组件时调用它。

const LogoutHandler = ({ history }) => {
  useEffect(() => {
    Cookies.remove("session");
  }, []);

  return <div>Logging out!</div>;
};