После того, как мой редукс отправляет действие logout
, я хочу вернуться в исходное состояние. Я использую nextjs
и не понимаю, почему мой желаемый редуктор выхода из системы в authenticateSlice.js
не выполняется. Я также использовал redux-persist
, чтобы сохранить свое состояние постоянным.
Вот мой код из store.js
import { configureStore } from "@reduxjs/toolkit";
import authenticateSlice from "./slices/authenticateSlice";
import { persistReducer, persistStore } from "redux-persist"; // import persistStore
import storage from "./storage";
import { encryptTransform } from "redux-persist-transform-encrypt";
import { combineReducers } from "redux";
import thunk from "redux-thunk";
const reducers = combineReducers({
auth: authenticateSlice.reducer,
});
let transforms = null;
if (process.env.NODE_ENV === "production") {
const encrypt = encryptTransform({
secretKey: process.env.NEXT_PUBLIC_REDUX_SECRET,
onError: function (error) {
// Handle the error.
},
});
transforms = [encrypt];
}
const persistConfig = {
key: "root",
storage: storage,
transforms,
};
const persistedReducer = persistReducer(persistConfig, reducers);
const store = configureStore({
reducer: persistedReducer,
middleware: [thunk],
});
let persistor = persistStore(store); // initialize persistor
export { store, persistor };
Вот мой код из authenticateSlice.js
, который у меня есть функция logout
для отправки.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { commonPostRequest } from "@/django-api/common/commonRequestAPI";
import { CommonPostHeader } from "@/django-api/common/commonHeadersAPI";
export const fetchUserLogin = createAsyncThunk(
"auth/login",
async (arg, { rejectWithValue }) => {
const { username, password } = arg;
try {
const loginRequest = await commonPostRequest({
body: { username: username, password: password },
headers: CommonPostHeader,
url: "http://localhost:8000/api/accounts_app/login",
});
if (loginRequest.data) {
return loginRequest.data;
} else {
throw new Error(loginRequest.message);
}
} catch (err) {
return rejectWithValue(err.message);
}
}
);
export const logout = () => {
return { type: "auth/logout" };
};
// loading: 'idle' | 'pending' | 'succeeded' | 'failed'
const initialState = {
data: {},
loading: "idle",
message: "",
};
const authenticateSlice = createSlice({
name: "auth",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserLogin.pending, (state) => {
state.loading = "pending";
})
.addCase(fetchUserLogin.fulfilled, (state, action) => {
state.loading = "succeeded";
state.data = action.payload;
})
.addCase(fetchUserLogin.rejected, (state, action) => {
state.loading = "failed";
state.message = action.payload;
})
.addCase(logout, (state) => {
Object.assign(state, initialState);
});
},
});
export default authenticateSlice;
Наконец, вот мой компонент функции Header
, в котором я хочу отправить действие logout
из среза или отправить функцию выхода из системы, чтобы вернуться с моим начальным состоянием.
import { Typography } from "@mui/material";
import { Row, Col, Divider, notification } from "antd";
import styles from "./Header.module.scss";
import Image from "next/image";
import Link from "next/link";
import {
toLocalZoneDateTime,
getCurrentDateTimeStamp,
} from "../common/functions/datetime";
import { useDispatch } from "react-redux";
import { useRouter } from "next/router";
import { logout } from "../../next-redux/slices/authenticateSlice";
const Header = (props) => {
const dispatch = useDispatch();
const { user } = props;
const router = useRouter();
const currentDateTimeStamp = getCurrentDateTimeStamp();
const handleLogoutClick = (e) => {
e.preventDefault();
console.info("logout");
dispatch(logout());
};
return (
<>
<Row>
<Col span = {24}>
<Row gutter = {[8, 8]} className = {styles.topBar}>
<Col span = {12}>
<Typography variant = "subtitle2" className = {styles.topDate}>
{toLocalZoneDateTime(currentDateTimeStamp)}
</Typography>
</Col>
<Col span = {12}>
{user ? (
<Typography align = "right">
<a
onClick = {(e) => {
handleLogoutClick(e);
}}
className = {styles.topBarLinks}
>
Logout
</a>
</Typography>
) : (
<>
<Typography align = "right">
{" "}
<Link href = "/signin/" className = {styles.topBarLinks}>
Sign-in
</Link>{" "}
<Link href = "/signup/" className = {styles.topBarLinks}>
Sign-up
</Link>
</Typography>
</>
)}
</Col>
</Row>
</Col>
</Row>
</>
);
};
export default Header;
Вы используете избыточное сохранение, поэтому вам нужно выйти из системы по-другому.
import { persistor } from './store'
const handleLogout = () => {
dispatch({ type: 'LOGOUT' })
persistor.purge()
}
Я думаю, что purge
очищает все сохраненные данные, не так ли?
Да, purge только «удаляет состояние с диска и возвращает промис», он ничего не делает с текущим значением хранилища Redux в памяти. Этот код не исправит фактический сброс среза состояния, как того хочет OP. Он работает только путем удаления сохраненного значения, и когда страница перезагружается, нет ничего, что могло бы повторно гидратировать состояние, поэтому используется начальное значение.
logout
— это функция, а не объект действия, поэтому дополнительный редьюсер не работает.
export const logout = () => {
return { type: "auth/logout" };
};
...
.addCase(logout, (state) => { // <-- function, e.g. logout.type undefined
Object.assign(state, initialState);
});
Решением будет объявить logout
как созданное действие с помощью утилиты createAction.
import { createSlice, createAction, createAsyncThunk } from "@reduxjs/toolkit";
import { commonPostRequest } from "@/django-api/common/commonRequestAPI";
import { CommonPostHeader } from "@/django-api/common/commonHeadersAPI";
export const fetchUserLogin = createAsyncThunk(
...
);
export const logout = createAction("auth/logout");
const initialState = { ... };
const authenticateSlice = createSlice({
name: "auth",
initialState,
extraReducers: (builder) => {
builder
.addCase(fetchUserLogin.pending, (state) => {
state.loading = "pending";
})
.addCase(fetchUserLogin.fulfilled, (state, action) => {
state.loading = "succeeded";
state.data = action.payload;
})
.addCase(fetchUserLogin.rejected, (state, action) => {
state.loading = "failed";
state.message = action.payload;
})
.addCase(logout, (state) => {
return initialState;
});
},
});
export default authenticateSlice.reducer;
Или объявите редуктор logout
, который будет генерировать действие logout
, которое можно будет экспортировать.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { commonPostRequest } from "@/django-api/common/commonRequestAPI";
import { CommonPostHeader } from "@/django-api/common/commonHeadersAPI";
export const fetchUserLogin = createAsyncThunk(
...
);
const initialState = { ... };
const authenticateSlice = createSlice({
name: "auth",
initialState,
reducers: {
logout: (state) => {
return initialState;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUserLogin.pending, (state) => {
state.loading = "pending";
})
.addCase(fetchUserLogin.fulfilled, (state, action) => {
state.loading = "succeeded";
state.data = action.payload;
})
.addCase(fetchUserLogin.rejected, (state, action) => {
state.loading = "failed";
state.message = action.payload;
});
},
});
export const { logout } = authenticateSlice.actions;
export default authenticateSlice.reducer;
это сработало!. но мне нужно обновить его, прежде чем он изменит мой заголовок обратно на «Войти и зарегистрироваться». Можете ли вы помочь мне избежать обновления страницы? Вот что я сделал const handleLogoutClick = (e) => { e.preventDefault(); console.info("Выход из системы"); диспетчеризация (выход пользователя ()); персистент.purge(); роутер.push("/"); };