I am working on login/register components in React, and I'm using useContext and useReducer hooks to manage state. This is the first time I've tried it this way, and I'm not sure why the state is not changing. Below are the files for the login component. I've shown where I've console logged and what the results are.
This is the api:
export const login = ({ email, password }) => {
console.log(email, password);
// [email protected] 12345678
return fetch(`${DEV_AUTH_URL}/signin`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
password,
}),
})
.then((res) => {
return res.ok
? res.json()
: res.json().then(err => PromiseRejectionEvent.reject(err));
})
.then(data => data);
};
This is the state manager:
const AuthState = (props) => {
const initialState = {
token: null,
isAuth: false,
errorMsg: null,
user: {},
};
const [state, dispatch] = useReducer(AuthReducer, initialState);
const history = useHistory();
const handleLogin = (formData) => {
login(formData)
.then((res) => {
console.log(res);
// {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7I…zk3fQ.Qx9zDeXBecToIEScCTDXzkBiTnATHab4cnyg0aSMdLE"}
res && res.token
? dispatch({ type: LOGIN_SUCCESS, payload: res })
: dispatch({ type: LOGIN_FAIL, payload: res });
})
.then(() => {
closeAllPopups();
console.log('jwt: ', localStorage.getItem('jwt'));
// {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7I…zk3fQ.Qx9zDeXBecToIEScCTDXzkBiTnATHab4cnyg0aSMdLE"}
console.log('token: ', state.token);
// token: null
console.log('isAuth: ', state.isAuth);
// isAuth: false
history.push('/');
})
.catch((err) => dispatch({ type: LOGIN_FAIL, payload: err.toString() }));
};
This is what is in the reducer:
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
} from '../types';
export default (state, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
localStorage.setItem('jwt', action.payload.token);
return {
...state,
token: action.payload.token, // this is not changing state
isAuth: true, // this is also not changing state
};
case LOGIN_FAIL:
return {
...state,
token: null,
user: {},
isAuth: false,
errorMsg: action.payload,
};
default:
return state;
}
};
It's a bit unclear what you really want the logic flow to be, but React state updates are asynchronous and the state from the render cycle the handleLogin
callback is invoked in is enclosed in callback scope for the life of that function. Just because React state updates are asynchronous doesn't mean they can be awaited on either.
From what I can tell you want to call login
, and upon login success dispatch an action, close popups, log some values, and navigate home. Other than logging updated state this can all be completed in the first thenable
.
const handleLogin = (formData) => {
login(formData)
.then((res) => {
console.log(res);
res?.token
? dispatch({ type: LOGIN_SUCCESS, payload: res })
: dispatch({ type: LOGIN_FAIL, payload: res });
closeAllPopups();
history.push('/');
})
.catch((err) => {
dispatch({ type: LOGIN_FAIL, payload: err.toString() });
});
};
Use an useEffect
hook to log any state updates.
useEffect(() => {
console.log('jwt: ', localStorage.getItem('jwt'));
console.log('token: ', state.token);
console.log('isAuth: ', state.isAuth);
}, [state]);