reactjs Redux:在分派之间检测到状态突变

laik7k3q  于 2023-03-12  发布在  React
关注(0)|答案(1)|浏览(110)

我试图实现项目的身份验证部分,但在登录页面,当我单击登录按钮时,它总是显示此错误:

Uncaught Error: Invariant failed: A state mutation was detected between dispatches, in the path 'auth.access'.  This may cause incorrect behavior. (https://redux.js.org/style-guide/style-guide#do-not-mutate-state)
   at invariant (immutableStateInvariantMiddleware.ts:25:1)
   at immutableStateInvariantMiddleware.ts:256:1
   at Object.measureTime (utils.ts:10:1)
   at Object.dispatch (immutableStateInvariantMiddleware.ts:249:1)
   at dispatch (<anonymous>:1:55003)
   at onClickLogIn (LogInPage.js:50:1)
   at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
   at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
   at invokeGuardedCallback (react-dom.development.js:4277:1)
   at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:1)

按照它的建议,我试着在onClickLogIn()函数中调试,但没有找到原因,我正在使用@reduxjs/toolkit,所以我认为它帮助我管理了reducer中的不变,然而,即使我改变了reducer中的代码,使其以不可变的方式显式地改变了一切。同样的错误一次又一次的出现。2有人能帮我看看原因吗?3非常感谢!4下面是我的代码片段:
store.js

import {configureStore} from "@reduxjs/toolkit";
import authReducer from './authSlice';
import requestReducer from './requestSlice';
import {getAccessToken, getRefreshToken, setAccessToken, setRefreshToken} from "../utils/token";
import * as apiCalls from '../api/apiCalls'
let preloadedState = {
    auth: {
        access: '',
        refresh: '',
        loggedIn: false,
    },
    request: {
        entities: [],
    }
};

const refreshToken = getRefreshToken();
if (refreshToken !== '')    {
    apiCalls.refresh({'refresh': refreshToken}).then(resp => {
        setAccessToken(resp.data.access);
        setRefreshToken(resp.data.refresh);
        preloadedState.auth.access = getAccessToken();
        preloadedState.auth.refresh = getRefreshToken();
        preloadedState.auth.loggedIn = true;
        apiCalls.setAuthHeader(resp.data.access);
        console.log('detect previous logged in: ', preloadedState);
    }).catch(err => {
        console.log('no auth / expired token in localstorage');
    });
}
const store = configureStore({
    reducer: {
        auth: authReducer,
        request:requestReducer,
    },
    preloadedState: preloadedState
});

const updateAuth = (state) => {
    const access = state.auth.access;
    const refresh = state.auth.refresh;
    setAccessToken(access);
    setRefreshToken(refresh);
    apiCalls.setAuthHeader(access);
    console.log('detect change in auth.')
}

console.log(store.getState());
store.subscribe(() => {
    updateAuth(store.getState());
});

export default store;

authSlice.js

import {createSlice} from "@reduxjs/toolkit";
import * as apiCalls from '../api/apiCalls'

const authSlice = createSlice({
    name: 'auth',
    initialState: null,
    reducers: {
        assignToken(state, action) {
            return {
                loggedIn: true,
                access: action.payload.access,
                refresh: action.payload.refresh,
            };
        },
        logOutUser(state, action) {
            return {
                loggedIn: false,
                access: '',
                refresh: '',
            };
        },
    }
});

export const {
    assignToken,
    logOutUser,
} = authSlice.actions

export default authSlice.reducer;

export function handleLogin(confidential) {
    return async function handleLoginThunk(dispatch, getState) {
        return apiCalls.getToken(confidential).then((response) => {
                console.log('log in: ', response);
                dispatch(assignToken({...response.data}));
            }
        ).catch((err) => {
            return Promise.reject(err.message);
        });
    }
};

export function handleRefresh(refresh) {
    return async function handleRefreshThunk(dispatch, getState) {
        apiCalls.refresh({'refresh': refresh}).then((response) => {
                console.log('refresh token:', ...response);
                dispatch(assignToken(...response.data));
            }
        ).catch((err) => {
        });
    }
};

export function handleLogOut() {
    return async function handleLogOutThunk(dispatch, getState) {
        const refreshToken = getState().auth.refresh;
        console.log('logout: ', refreshToken);
        dispatch(logOutUser());
        apiCalls.logout({refresh_token: refreshToken})
            .then((response) => {
            }).catch(error => {
        });
    }
};

LoginPage.js

import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
import TextField from '@mui/material/TextField';
import Link from '@mui/material/Link';
import Paper from '@mui/material/Paper';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import {useDispatch, useSelector} from "react-redux"
import {createTheme, ThemeProvider} from '@mui/material/styles';
import {useEffect, useState} from "react";
import * as apiCalls from "../api/apiCalls";
import {clearErrorMessage, handleLogin, handleLogOut, handleRefresh, logInUser} from "../redux/authSlice";
import Copyright from "../components/Copyright";
import axios from "axios";
import {useNavigate} from "react-router-dom";
import {Alert} from "@mui/material";
import {getRefreshToken} from "../utils/token";

const theme = createTheme();

export default function SignInSide() {
    const navigate = useNavigate();
    const [apiCallPending, setApiCallPending] = useState(false);
    const [apiError, setApiError] = useState('');
    const [role, setRole] = useState('Staff');
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const loggedIn = useSelector(state => state.loggedIn);
    const errMsg = useSelector(state => state.message);
    const dispatch = useDispatch();
    const nextRole = () => {
        return role === 'Staff' ? 'Patient' : 'Staff';
    };
    // useEffect(() => {
    //     if (loggedIn)
    //         navigate('/dashboard', {state: {from: '/login'}});
    // });
    const onClickChangeRole = () => {
        setRole(nextRole());
    }
    const onClickLogIn = (event) => {
        event.preventDefault();
        setApiCallPending(true);
        const data = {username: username, password: password};
        dispatch(handleLogin(data)).then(response => {
            console.log('Log In Successfully: ', response);
            navigate('/dashboard', {state: {from: '/login'}});
        }).catch(err => {
        });
    };



    return (
        <ThemeProvider theme={theme}>
            <Grid container component="main" sx={{height: '100vh'}}>
                <CssBaseline/>
                <Grid
                    item
                    xs={false}
                    sm={4}
                    md={7}
                    sx={{
                        backgroundImage: 'url(https://images.unsplash.com/photo-1594824476967-48c8b964273f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MjF8fGRvY3RvcnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=400&q=60)',
                        backgroundRepeat: 'no-repeat',
                        backgroundColor: (t) =>
                            t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
                        backgroundSize: 'cover',
                        backgroundPosition: 'center',
                    }}
                />
                <Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
                    <Box
                        sx={{
                            my: 8,
                            mx: 4,
                            display: 'flex',
                            flexDirection: 'column',
                            alignItems: 'center',
                        }}
                    >
                        <Avatar sx={{m: 1, bgcolor: 'secondary.main'}}>
                            <LockOutlinedIcon/>
                        </Avatar>
                        <Typography component="h1" variant="h5">
                            {role} Sign in
                        </Typography>
                        <Box component="form" noValidate sx={{mt: 1}}>
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                id="username"
                                label="Username"
                                name="username"
                                value={username}
                                autoComplete="username"
                                autoFocus
                                onChange={(event) => {
                                    setUsername(event.target.value)
                                }}
                            />
                            {role === 'Staff' && <TextField
                                margin="normal"
                                required
                                fullWidth
                                name="password"
                                label="Password"
                                type="password"
                                id="password"
                                value={password}
                                onChange={(event) => {
                                    setPassword(event.target.value)
                                }}
                                autoComplete="current-password"
                            />}
                            {/*<FormControlLabel*/}
                            {/*    control={<Checkbox value="remember" color="primary"/>}*/}
                            {/*    label="Remember me"*/}
                            {/*/>*/}

                            {apiError !== '' && <Alert severity="error">{apiError}</Alert>}
                            <Button
                                type="submit"
                                fullWidth
                                variant="contained"
                                onClick={onClickLogIn}
                                sx={{mt: 3, mb: 2}}
                            >
                                Sign In
                            </Button>
                            <Button
                                onClick={onClickChangeRole}
                                fullWidth
                                variant="contained"
                                sx={{mb: 2}}
                            >
                                {`${nextRole()} Log In`}
                            </Button>
                            <Grid container>
                                <Grid item xs>
                                    <Link href="#" variant="body2">
                                        Forgot password?
                                    </Link>
                                </Grid>
                                <Grid item>
                                    <Link href="#" variant="body2">
                                        {"Don't have an account? Sign Up"}
                                    </Link>
                                </Grid>
                            </Grid>
                            <Copyright sx={{mt: 5}}/>
                        </Box>
                    </Box>
                </Grid>
            </Grid>
        </ThemeProvider>
    );
}
vawmfj5a

vawmfj5a1#

是的,根据错误消息,您正在修改路径state.auth.access,令牌逻辑中的这一行显然正在执行此操作:

preloadedState.auth.access = getAccessToken();

*不要 * 改变现有的状态!而是分派一个包含新令牌的操作,并在Reducer中更新该状态。

相关问题