我正在尝试向我的应用程序添加身份验证并使用 React Context API 维护身份验证状态。
我正在使用自定义钩子 use-http 调用我的 api。
const
array1 = [
{ 'Full Name': 'ACC stu',
Email: 'test@gmail.com',
Status: 'Cold',
Tel: '1111111'
},
{
'Full Name': 'ADD stu',
Email: 'test2@gmail.com',
Status: 'Cold',
Tel: '222222'
}
],
array2 = [
{ address: 'XXXX',
city: 'AAAAA'
},
{
address: 'QQQQ',
city: 'ZZZZ'
}
];
const res = array1.map((obj, i) => ({ ...obj, ...(array2[i] || {}) }));
console.log(res);
这是我的登录页面,它调用 api,我需要离开此页面并更新我的身份验证上下文。
import { useCallback, useReducer } from 'react';
function httpReducer(state, action) {
switch (action.type) {
case 'SEND':
return {
data: null,
error: null,
status: 'pending',
};
case 'SUCCESS':
return {
data: action.responseData,
error: null,
status: 'completed',
};
case 'ERROR':
return {
data: null,
error: action.errorMessage,
status: 'completed',
};
default:
return state;
}
}
function useHttp(requestFunction, startWithPending = false) {
const [httpState, dispatch] = useReducer(httpReducer, {
status: startWithPending ? 'pending' : null,
data: null,
error: null,
});
const sendRequest = useCallback(
async requestData => {
dispatch({ type: 'SEND' });
try {
const responseData = await requestFunction(requestData);
dispatch({ type: 'SUCCESS', responseData });
} catch (error) {
dispatch({
type: 'ERROR',
errorMessage: error.response.data.message || 'Something went wrong!',
});
}
},
[requestFunction]
);
return {
sendRequest,
...httpState,
};
}
export default useHttp;
登录api -
import { useCallback, useContext } from 'react';
import { makeStyles } from '@material-ui/core';
import Container from '@material-ui/core/Container';
import LoginForm from '../components/login/LoginForm';
import useHttp from '../hooks/use-http';
import { login } from '../api/api';
import AuthContext from '../store/auth-context';
import { useEffect } from 'react';
const useStyles = makeStyles(theme => ({
pageWrapper: {
height: '100vh',
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.default,
},
pageContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
flexGrow: '1',
},
}));
function Login() {
const authCtx = useContext(AuthContext);
const { sendRequest, status, data: userData, error } = useHttp(login);
const loginHandler = (email, password) => {
sendRequest({ email, password });
};
if (status === 'pending') {
console.log('making request');
}
if (status === 'completed' && userData) {
console.log('updateContext');
authCtx.login(userData);
}
if (status === 'completed' && error) {
console.log(error);
}
const classes = useStyles();
return (
<div className={classes.pageWrapper}>
<Container maxWidth="md" className={classes.pageContainer}>
<LoginForm status={status} onLoginHandler={loginHandler} />
</Container>
</div>
);
}
export default Login;
身份验证上下文 -
export const login = async ({ email, password }) => {
let config = {
method: 'post',
url: `${BACKEND_URL}/api/auth/`,
headers: { 'Content-Type': 'application/json' },
data: {
email: email,
password: password,
},
};
const response = await axios(config);
return response.data;
};
问题是当我在登录组件中调用我的 AuthContext 的 loginHandler 函数时,登录组件重新渲染并且这个登录函数进入无限循环。我做错了什么?
我是 React 的新手,几个小时以来一直在解决这个问题。
答案 0 :(得分:1)
我想我知道它是什么。
您通过钩子引入了一堆组件状态。每当 authCtx
、sendRequest
、status
、data
和 error
发生变化时,组件都会重新渲染。避免将关闭放入状态。闭包会触发不必要的重新渲染。
function Login() {
const authCtx = useContext(AuthContext);
const { sendRequest, status, data: userData, error } = useHttp(login);
尝试查找所有可能导致重新渲染的闭包,并确保组件不依赖它们。
编辑:
Ben West 是对的 - 您在渲染过程中也会出现副作用,这是错误的。
当您在功能组件的主体中有这样的东西时:
if (status === 'completed' && userData) {
console.log('updateContext');
authCtx.login(userData);
}
改成这样:
useEffect(() => {
if (status === 'completed' && userData) {
console.log('updateContext');
authCtx.login(userData);
}
}, [status, userData]); //the function in arg 1 is called whenever these dependencies change
我对您的代码进行了大量更改:
只有 2 个文件。我内联的其他内容。
我对 useContext() 不太熟悉,所以我不能说你是否正确使用它。
Login.js
:
import { useContext, useEffect } from 'react';
import { makeStyles } from '@material-ui/core';
import Container from '@material-ui/core/Container';
import LoginForm from '../components/login/LoginForm';
import AuthContext from '../store/auth-context';
const useStyles = makeStyles(theme => ({
pageWrapper: {
height: '100vh',
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.default,
},
pageContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
flexGrow: '1',
},
}));
function httpReducer(state, action) {
switch (action.type) {
case 'SEND':
return {
data: null,
error: null,
status: 'pending',
};
case 'SUCCESS':
return {
data: action.responseData,
error: null,
status: 'completed',
};
case 'ERROR':
return {
data: null,
error: action.errorMessage,
status: 'completed',
};
default:
return state;
}
}
function Login() {
const [httpState, dispatch] = useReducer(httpReducer, {
status: startWithPending ? 'pending' : null,
data: null,
error: null,
});
const sendRequest = async requestData => {
dispatch({ type: 'SEND' });
try {
let config = {
method: 'post',
url: `${BACKEND_URL}/api/auth/`,
headers: { 'Content-Type': 'application/json' },
data: {
email: requestData.email,
password: requestData.password,
},
};
const response = await axios(config);
dispatch({ type: 'SUCCESS', responseData: response.data });
} catch (error) {
dispatch({
type: 'ERROR',
errorMessage: error.response.data.message || 'Something went wrong!',
});
}
};
const authCtx = useContext(AuthContext);
const loginHandler = (email, password) => {
sendRequest({ email, password });
};
useEffect(() => {
if (httpState.status === 'pending') {
console.log('making request');
}
}, [httpState.status]);
useEffect(() => {
if (httpState.status === 'completed' && httpState.data) {
console.log('updateContext');
authCtx.login(httpState.data);
}
}, [httpState.status, httpState.data]);
useEffect(() => {
if (httpState.status === 'completed' && httpState.error) {
console.log(httpState.error);
}
}, [httpState.status, httpState.error]);
const classes = useStyles();
return (
<div className={classes.pageWrapper}>
<Container maxWidth="md" className={classes.pageContainer}>
<LoginForm status={httpState.status} onLoginHandler={loginHandler} />
</Container>
</div>
);
}
export default Login;
AuthContext.js
:
import React, { useState } from 'react';
import { useEffect } from 'react';
import {
getUser,
removeUser,
saveUser,
getExpirationTime,
clearExpirationTime,
setExpirationTime,
} from '../utils/local-storage';
const AuthContext = React.createContext({
token: '',
isLoggedIn: false,
login: () => {},
logout: () => {},
});
const calculateRemainingTime = expirationTime => {
const currentTime = new Date().getTime();
const adjExpirationTime = new Date(expirationTime).getTime();
const remainingDuration = adjExpirationTime - currentTime;
return remainingDuration;
};
// is this asynchronous?
const retrieveStoredToken = () => {
const storedToken = getUser();
const storedExpirationDate = getExpirationTime();
const remainingTime = calculateRemainingTime(storedExpirationDate);
if (remainingTime <= 60) {
removeUser();
clearExpirationTime();
return null;
}
return {
token: storedToken,
duration: remainingTime,
};
};
export const AuthContextProvider = ({ children }) => {
const [tokenData, setTokenData] = useState(null);
const [logoutTimer, setLogoutTimer] = useState(null);
useEffect(() => {
const tokenData_ = retrieveStoredToken(); //is this asynchronous?
if (tokenData_) {
setTokenData(tokenData_);
}
}, []);
const userIsLoggedIn = !!(tokenData && tokenData.token);
const logoutHandler = () => {
setTokenData(null);
removeUser();//is this asynchronous?
clearExpirationTime();
if (logoutTimer) {
clearTimeout(logoutTimer);
//clear logoutTimer state here? -> setLogoutTimer(null);
}
};
const loginHandler = ({ token, user }) => {
console.log('login Handler runs');
console.log(token, user.expiresIn);
setTokenData({ token });
saveUser(token);
setExpirationTime(user.expiresIn);
const remainingTime = calculateRemainingTime(user.expiresIn);
setLogoutTimer(setTimeout(logoutHandler, remainingTime));
};
useEffect(() => {
if (tokenData && tokenData.duration) {
console.log(tokenData.duration);
setLogoutTimer(setTimeout(logoutHandler, tokenData.duration));
}
}, [tokenData]);
const user = {
token: tokenData.token,
isLoggedIn: userIsLoggedIn,
login: loginHandler,
logout: logoutHandler,
};
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
};
export default AuthContext;