Reputation: 301
I'm using Redux Toolkit (RTK) Query for making API calls, using RTK for state management. The issue I'm facing is that every time I log in, the user information that I get from backend isn't automatically updated from the localStorage and needs to be refreshed once before the data is accessed in the userSlice
. I'm using extraReducers
to automatically fetch the data from userLogin
endpoint and storing it in localStorage. I'm again accessing the localStorage data and providing it as the initial state of the userSlice
.
This is my User Slice.
import { createSlice } from "@reduxjs/toolkit";
import Apiservice from "../service/apiService";
const initialState = {
userInfo: localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null,
};
const userSlice = createSlice({
name: "userSlice",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addMatcher(
Apiservice.endpoints.userLogin.matchFulfilled,
(state, action) => {
localStorage.setItem("userInfo", JSON.stringify(action.payload));
}
);
builder.addMatcher(
Apiservice.endpoints.getMyDetails.matchFulfilled,
(state, action) => {
localStorage.setItem("userInfo", JSON.stringify(action.payload));
}
);
},
});
export const {} = userSlice.actions;
export default userSlice.reducer;
This is the Login
component where I need to manually call window.location.reload()
- not doing which will not give me the userInfo
data from userSlice
.
const Login = () => {
const [showPass, setShowPass] = useState(false);
const [showPass1, setShowPass1] = useState(false);
const {
control,
watch,
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: {
email: "",
password: "",
},
});
const toast = useToast();
const navigate = useNavigate();
const email = watch("email");
const password = watch("password");
const [userLogin, { isLoading, isError, isSuccess, error, data }] =
useUserLoginMutation();
useEffect(() => {
if (isSuccess) {
toast({
title: "Login Successful.",
status: "success",
duration: 9000,
isClosable: true,
});
navigate("/");
window.location.reload();
}
if (isError) {
toast({
title: error?.data?.message,
status: "error",
duration: 5000,
isClosable: true,
});
}
}, [isSuccess, toast, navigate, isError, error?.data]);
const onSubmit = (data) => {
userLogin(data);
};
...
When I first log in the userInfo
data I get using useSelector
is empty and gets filled with localStorage data once I refresh.
I'm assuming there's something asynchronous going on with how redux store stores/retrieves information, but I'm confused as to which approach I should take. Also what to do when there's an api call which updates user information? I should refresh every time that happens as well?
Upvotes: 2
Views: 1065
Reputation: 203208
You appear to have a bit of a misunderstanding of several key concepts:
Ignoring state persistence for now, the reducer functions should just update the state with the value you want the state to have. When the state is updated this will trigger the subscribed (to that state) components to rerender and receive the current userInfo
state value.
Example:
const initialState = {
userInfo: localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null,
};
const userSlice = createSlice({
name: "userSlice",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addMatcher(
Apiservice.endpoints.userLogin.matchFulfilled,
(state, action) => {
state.userInfo = action.payload;
}
)
.addMatcher(
Apiservice.endpoints.getMyDetails.matchFulfilled,
(state, action) => {
state.userInfo = action.payload;
}
);
},
});
In a pinch, you could certainly additionally update the localStorage from the reducer function, but this would be considered anti-pattern by many and should be avoided as much as possible.
const userSlice = createSlice({
name: "userSlice",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addMatcher(
Apiservice.endpoints.userLogin.matchFulfilled,
(state, action) => {
state.userInfo = action.payload;
localStorage.setItem("userInfo", JSON.stringify(action.payload));
}
)
.addMatcher(
Apiservice.endpoints.getMyDetails.matchFulfilled,
(state, action) => {
state.userInfo = action.payload;
localStorage.setItem("userInfo", JSON.stringify(action.payload));
}
);
},
});
It would be better to issue the side-effect from an action, e.g. in the onQueryStarted
property of the API slice's queries/mutations, or simply integrating a redux state persistence solution/library like redux-persist
(see also persistence and rehydration).
Upvotes: 1