即使依赖没有改变,useEffect 每次都会继续执行

时间:2021-06-16 14:53:19

标签: reactjs

我们有 Node * removeDuplicates( Node *head) { map <int, int> duplicates; Node* temp=head; while(temp){ duplicates[temp->data]=1; temp=temp->next; } Node* curr=head; Node* prev=NULL; while(curr){ if(duplicates[curr->data]==1){ duplicates[curr->data]=0; prev=curr; } else{ prev->next=curr->next; delete(curr); } curr=prev->next; } return head; } 用于设置我们可以在整个应用程序中使用的用户对象。我们的 UserContext 每次都继续执行,并且即使依赖项没有改变也不必要地进行 api 调用。

UserContext

每当我们导航到 React 应用程序中的不同页面时,我们都会看到“用户”路由每次都被调用,即使令牌没有更新。我们的令牌仅在用户注销时更改。

我们的 AppRouter 如下所示;

    import React, { useState, useEffect } from 'react'
    import APIService from './utils/APIService';
    import { getCookies } from './utils/Helper';

    const UserContext = React.createContext();

    const UserContextProvider = (props) => {
        const [token, setToken] = useState(getCookies('UserToken'));
      const [user, setUser] = useState(null);
        
        useEffect(() => {
            console.log('Inside userContext calling as token ', token)
        fetchUserInfo();

        }, [token]);

        const fetchUserInfo = async() => {
            if (token) {
                let userRes = await APIService.get(`/user?token=${token}`);
                console.log('User route called')
                setUser(userRes.data);
            }      
        }

        /* 
            If user logoff or login, update token from child component
        */
      const refreshToken = (newToken) => {
            //token = newToken;
            setToken(newToken);
            fetchUserInfo()
      }

        return (
            <UserContext.Provider value={{user, token, refreshToken}}>
                {props.children}
            </UserContext.Provider>
        );
    }
    export { UserContextProvider,  UserContext }

这是我们的内部应用,因此我们希望用户可以登录 30 天,而不必每次都保持登录状态。因此,当用户第一次登录时,我们为他们创建一个令牌并将该令牌保存在 cookie 中。因此,如果用户关闭浏览器并再次返回,我们会在 cookie 中检查令牌。如果令牌存在,我们会调用 API 来获取用户信息并在我们的上下文中设置用户。这是不工作的部分,它在导航到应用程序中的每条路线的过程中不断调用我们的用户 api。

这是我们的 login.js

    import React from 'react';
    import AppRouter from "./AppRouter";
    import { Container } from 'react-bootstrap';
    import Header from './components/Header';
    import { ToastProvider, DefaultToastContainer } from 'react-toast-notifications';
    import 'bootstrap/dist/css/bootstrap.css';
    import './scss/styles.scss';

    import { UserContextProvider } from './UserContextProvider';

    export default function App() {
      const ToastContainer = (props) => (
        <DefaultToastContainer
          className="toast-container"
          style={{ zIndex:100,top:50 }}
          {...props}
        />
      );

      return (
        <UserContextProvider>
          <ToastProvider autoDismiss={true} autoDismissTimeout={3000} components={{ ToastContainer }}>
            <Container fluid>
              <Header />
              <AppRouter />
            </Container>
          </ToastProvider>
        </UserContextProvider>
      )
    }

我们的 userContext 如下所示

    import React, { useState, useContext } from 'react';
    import { setCookies } from '../../utils/Helper';
    import APIService from '../../utils/RestApiService';
    import { UserContext } from '../../UserContextProvider';
    import queryString from 'query-string';
    import './_login.scss';

    const Login = (props) => {
      const [email, setEmail] = useState(null);
      const [password, setPassword] = useState(null);
      const [error, setError] = useState(null);
      const { siteId } = props;
      const { refreshToken} = useContext(UserContext);

      const onKeyPress = (e) => {
        if (e.which === 13) {
          attemptLogin()
        }
      }

      let params = queryString.parse(props.location.search)
      let redirectTo = "/"
      if (params && params.redirect)
        redirectTo = params.redirect
      
      const attemptLogin = async () => {
        const payload = {
          email: email,
          password: password,
          siteid: siteId
        };
        let response = await APIService.post('/login', payload);
        console.log('response - ', response)
        if (response.status === 200) {
          const { data } = response;
          setCookies('UserToken', data.token);
          refreshToken(data.token)
          window.location.replace(redirectTo);
        }
        else {
          const { error } = response.data;
          setError(error);
        }
      }

      const renderErrors = () => {
        return (
          <div className="text-center login-error">
            {error}
          </div>
        )
      }


        return (
        <div className="login-parent">
          <div className="container">
            <div className="login-row row justify-content-center align-items-center">
              <div className="login-column">
                <div className="login-box">
                  <form className="login-form form">
                    <h3 className="login-form-header text-center">Login</h3>
                    <div className="form-group">
                      <label>Email:</label>
                      <br/>
                      <input
                        onChange={e => setEmail(e.target.value)}
                        placeholder="enter email address"
                        type="text"
                        onKeyPress={onKeyPress}
                        className="form-control"/>
                    </div>
                    <div className="form-group">
                      <label>Password:</label>
                      <br/>
                      <input
                        onChange={e => setPassword(e.target.value)}
                        placeholder="enter password"
                        type="password"
                        className="form-control"/>
                    </div>
                    <div className="form-group">
                      <button
                        className="btn btn-secondary btn-block"
                        onClick={attemptLogin}
                        type="button">
                        Login
                      </button>
                    </div>
                    {error ? renderErrors() : null}
                  </form>
                </div>
              </div>
            </div>
          </div>
        </div>
        )
    }

    export default Login;

我们的 getCookies 函数使用 Universal-cookies 包简单地读取 cookie

    import React, { useState, useEffect } from 'react'
    import APIService from './utils/APIService';
    import { getCookies } from './utils/Helper';

    const UserContext = React.createContext();

    const UserContextProvider = (props) => {
        const [token, setToken] = useState(getCookies('UserToken'));
      const [user, setUser] = useState(null);
        useEffect(() => {
            if (!token) return;
            console.log('Inside userContext calling as token ', token)
        fetchUserInfo();

        }, [token]);

        const fetchUserInfo = async() => {
            if (token) {
                let userRes = await APIService.get(`/user?token=${token}`);
                console.log('User route called')
                setUser(userRes.data);
            }      
        }

        /* 
            If user logoff or login, update token from child component
        */
      const refreshToken = (newToken) => {
            //token = newToken;
            setToken(newToken);
            fetchUserInfo()
      }

        return (
            <UserContext.Provider value={{user, token, refreshToken}}>
                {props.children}
            </UserContext.Provider>
        );
    }
    export { UserContextProvider,  UserContext }

1 个答案:

答案 0 :(得分:6)

所以我尝试使用 CodeSandbox 复制您的问题,这些是我根据您的代码发现的结果:

上下文:

您的上下文有一个 useEffect,它依赖于 token。当您调用 refreshToken 时,您会更新 token,它会自动触发 useEffect 并调用 fetchUserInfo。因此,您无需在 fetchUserInfo 中的 setToken 之后调用 refreshToken。您的上下文如下所示:


const UserContext = React.createContext();

const UserContextProvider = (props) => {
  const [token, setToken] = useState(getCookies("UserToken"));
  const [user, setUser] = useState(null);

  useEffect(() => {
    console.log("Inside userContext calling as token ", token);
    fetchUserInfo();
  }, [token]);

  const fetchUserInfo = async () => {
    if (token) {
      let userRes = await APIService.get(`/user?token=${token}`);
      console.log('User route called')
      setUser(userRes.data);
    }
  };

  const refreshToken = (newToken) => {
    setToken(newToken);
  };

  return (
    <UserContext.Provider value={{ user, token, refreshToken }}>
      {props.children}
    </UserContext.Provider>
  );
};
export { UserContextProvider, UserContext };

路线:

现在进入您的路由,因为您没有包含 AppRouter 的代码 我不得不假设您将 react-routerSwitch 组件一起使用. (如 CodeSandbox 中所示)。

我看到您的 Login 组件中有一行是 window.location.replace(redirectTo);。当您这样做时,整个页面会刷新(重新加载?)并且 React 会触发重新渲染,这就是我认为您的上下文方法再次触发的原因。

而是使用 history 中的 react-router API (再次,我的假设) 像这样,

let history = useHistory();
history.push(redirectTo);

如果你想玩,这里是沙盒:

Edit so-questions-68005144