I am using Redux and when I send a get request to authenticate user it is working, creating a token in cookies, but in Redux the state is not changing. What am I doing wrong? When reloading the page my token and user data are valid, here are some code samples.
After successful login my state is:
{
"user": {
"isAuthenticated": false
}
}
Yet is should be true
I logged in before useEffect
and it says true, because I have token its correct, so how do I pass it inside useEffect
or when page is opened? I want to toast when it is true.
User reducer:
import { createReducer } from "@reduxjs/toolkit"
const initialState = {
isAuthenticated: false,
}
export const userReducer = createReducer(initialState, (builder) => {
builder
.addCase('AuthenticateUserRequest', (state) => {
state.loading = true
})
.addCase('AuthenticateUserSuccess', (state, action) => {
state.isAuthenticated = true
state.user = action.payload
state.loading = false
})
.addCase('AuthenticateUserFailure', (state, action) => {
state.isAuthenticated = false
state.error = action.payload
state.loading = false
})
.addCase('ClearErrors', (state) => {
state.error = null
})
})
User action:
import axios from "axios"
import { authenticateUserUrl } from '../../constants'
export const authenticateUser = () => async (dispatch) => {
try {
dispatch({
type: 'AuthenticateUserRequest'
})
const { data } = await axios.get(authenticateUserUrl, { withCredentials: true })
dispatch({
type: 'AuthenticateUserSuccess',
payload: data.user,
})
} catch (err) {
dispatch({
type: 'AuthenticateUserFailure',
payload: err.response.data.message
})
}
}
store:
import { configureStore } from "@reduxjs/toolkit"
import { userReducer } from "./reducers/user"
const Store = configureStore({
reducer: {
user: userReducer,
},
})
export default Store
app:
import { useContext, useEffect } from "react"
import { authenticateUser } from "./redux/actions/user"
const App = () => {
var store = useContext(ReactReduxContext).store.getState()
useEffect(() => {
Store.dispatch(authenticateUser)
console.log(JSON.stringify(store))
})
5
Issue
The issue here is that of a stale Javascript closure. Redux state updates are synchronous, but you are attempting to console log the store
value from the render cycle prior to the effect running.
const App = () => {
var store = useContext(ReactReduxContext).store.getState(); // <-- accessed here
useEffect(() => {
Store.dispatch(authenticateUser) ; // <-- updates state
console.log(JSON.stringify(store)); // <-- still old value from outer closure
})
...
};
Solution
You can access the store
using the useStore
hook and then access the current state value in the effect callback.
const App = () => {
const store = useStore();
useEffect(() => {
store.dispatch(authenticateUser);
console.log(JSON.stringify(store.getState()));
}, []);
...
};
Using the useStore
hook and dispatch
and getState
methods are not the normal way of accessing the store in a React component. It’s far more common to use the useDispatch
and useSelector
hooks to dispatch actions to the store and subscribe to changes from the store. Use a separate effect to log the user state when it updates.
Example:
const App = () => {
const dispatch = useDispatch();
const user = useSelector(state => state.user);
useEffect(() => {
dispatch(authenticateUser);
}, [dispatch]);
useEffect(() => {
console.log(JSON.stringify(user));
}, [user]);
...
};
Well turns out the solution is:
const { isAuthenticated, user, loading, error } = useSelector((state) => state.user)
useEffect(() => {
Store.dispatch(authenticateUser()) // Dispatch the action once on component mount
}, []) // Empty dependency array to ensure this runs only once
useEffect(() => {
if (isAuthenticated) {
console.log("User State:", { isAuthenticated, user, loading, error })
}
}, [isAuthenticated, user, loading, error])