Current user data after login in React-Redux - reactjs

I'm working on an auth system in React-Redux.After successfull login, I want to keep the current data.I can't do it right tho.
ACTIONS CODE:
// ** LOGIN USER **
export const login = ({ email, password }) => (dispatch) => {
const body = JSON.stringify({ email, password });
axios
.post('/login', body, getHeaders())
.then(console.log(body))
.then((res) => {
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
history.push('/');
})
.then(() => {
dispatch({
type: USER_LOADED,
});
})
.catch((err) => {
console.log(err);
dispatch(returnErrors(err.message, err.id, 'LOGIN_FAIL'));
dispatch({
type: LOGIN_FAIL,
});
});
};
How can I store the user data in USER_LOADED type?It works only before the first reload.
REDUCERS CODE:
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
isLoading: false,
user: null,
profileInitialized: false,
profileData: null,
};
export default function (state = initialState, action) {
switch (action.type) {
// USER AUTHENTICATION
case USER_LOADING:
return {
...state,
isLoading: true,
};
case USER_LOADED:
return {
...state,
...action.payload,
isAuthenticated: true,
isLoading: false,
};
case LOGIN_SUCCESS:
case REGISTER_SUCCESS:
localStorage.setItem('token', action.payload.token);
return {
...state,
...action.payload,
isAuthenticated: true,
isLoading: false,
};
case AUTH_ERROR:
case LOGIN_FAIL:
case LOGOUT_SUCCESS:
case REGISTER_FAIL:
localStorage.removeItem('token');
+++
I should add that JWT stays after reload so user is connected but with no username shown for example.
Right after the login redirect
After Reload
I know the code
.then(() => {
dispatch({
type: USER_LOADED,
});
})
is wrong but I don't know how to make it work

I assume the user data is from POST /login. If this is the case, I think you need to remove .then(console.log(body)) first, since it doesn't return the response to the next .then function. Also, you need to return the user data down the promise chain. I hope that helps.
axios
.post('/login', body, getHeaders())
.then((res) => {
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
history.push('/');
return res.data; // pass the user data down to the promise chain
}).then((user) => {
dispatch({
type: USER_LOADED,
payload: user
});
})
EDIT
Based on the comment in the original question. The question is about persisting data in browser and pull it back when USER_LOADED is triggered.
I think you can use the local storage to store the user information just like how you do it with token for the initialState. So it will always try to use saved user info.
After the POST call, you can add the user info from the payload into the local storage. So it will still be available after page refresh.
axios
.post('/login', body, getHeaders())
.then((res) => {
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
// I assume user data is part of the `POST /login` payload
localStorage.setItem('user', res.data.user)
history.push('/');
return res.data; // pass the user data down to the promise chain
})
I hope this answers your question.

Related

Nextjs redux-thunk wanting to show an alert after dispatch but the alert isn’t seeing the error variable new state

As I mentioned in the title, I want to show an alert showing if the action was successful or not.
But when the alert is supposed to show an error it shows success. When I checked the error variable it says undefined but when I checked it by showing it on the website with JSX it showed the error and not undefined.
I'm using redux-thunk
My Code:
loginScreen.js
dispatch(login(email, password)).then(() => error ? alert(error) : alert("success"));
loginAction.js
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "application/json",
},
};
const { data } = await axios.post(
"/api/auth/login",
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("user", JSON.stringify(data.token));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
loginReducer.js
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true };
case USER_LOGIN_SUCCESS:
return { loading: false, userInfo: action.payload };
case USER_LOGIN_FAIL:
return { loading: false, error: action.payload };
case USER_LOGOUT:
return {};
default:
return state;
}};

Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'access')

I'm using react and redux to create simple user authentication. When I click on the login button the user get's authenticated, yet
the user remails null. Through debugging, I have come to realize that the error is from the the auth action. From the auth action function (found in the code below) with the variable nameload_user I do not understand the right URL to use. I understand that it's supposed to take a get request, but the login API I'm using cannot take a get request. By using GET request there I get method not allowed. It's the data obtained from this load_user that is dispatched in payload which is used to assess the ACCESS and REFRESH tokens in redux reducer. Because I'm not making the correct axios request react is unable to read the property of the access token that's been returned, hence I'm getting the named error.
Auth Action
_____________
import axios from 'axios';
import { LOGIN_SUCCESS, LOGIN_FAIL, USER_LOADED_SUCCESS, USER_LOADED_FAIL,} from './types';
export const load_user = () => async dispatch =>{
if(localStorage.getItem('access')){
const config = {
headers: {
'Content-Type':'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try{
const res = await axios.get(`${process.env.REACT_APP_API_URL}/account/login`, config)
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
})
}
catch (err){
dispatch({
type: USER_LOADED_FAIL,
})
}
}else {
dispatch({
type: USER_LOADED_FAIL,
})
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type':'application/json'
}
};
//const body = JSON.stringify({email, password});
const body = {email, password};
const data = JSON.stringify(body);
try{
console.log(data)
const res = await axios.post(`${process.env.REACT_APP_API_URL}/account/login`, data, config)
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
})
dispatch(load_user());
} catch (err){
dispatch({
type: LOGIN_FAIL,
})
}
}
Auth Reducer
import { LOGIN_SUCCESS, LOGIN_FAIL, USER_LOADED_SUCCESS, USER_LOADED_FAIL,} from '../actions/types'
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null
};
const authReducer = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case LOGIN_SUCCESS:
localStorage.setItem('access', payload.access);
return{
...state,
isAuthenticated: true,
access: payload.access,
refresh: payload.refresh
}
case USER_LOADED_SUCCESS:
localStorage.setItem('access', payload.access);
return{
...state,
user: payload
}
case USER_LOADED_FAIL:
localStorage.setItem('access', payload.access);
return{
...state,
user: null
}
case LOGIN_FAIL:
localStorage.removeItem('access');
localStorage.removeItem('refresh');
return {
...state,
isAuthenticated: false,
access: null,
refresh: null,
user: null
}
default:
return state;
}
}
export default authReducer
When ever I login the user, I get the following output from the django server console (NOTE that both status 200 and status 400 appear
on the console follow a single login)
[09/Nov/2021 10:05:28] "POST /account/login HTTP/1.1" 200 506
Bad Request: /user/login
[09/Nov/2021 10:05:28] "POST /account/login HTTP/1.1" 400 76
Login views.py
class LoginAPIView(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Please, my question is, how do I know the approximate url to send axios request since the login API cannot accept a get request?

Why is useReducer hook not updating state?

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);
// jennifer#jennifer.com 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;
}
};
Issue
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.
Solution
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]);

Showing 2 different states from the same reducer without the state rewrites itself

I'm building a large application that fetches different music audio and videos from the same reducer and I wanted to fetch the latest 5 music audios and videos from the reducer but both of them are in the same reducer, how do I get both data without them rewriting each other as I use the actions?
I don't get your question clearly and i think it better to post your code.
But if i got u right i think you are using the same object to store both of them.
Your code should be something like this.
at reducer.js
const INITIAL_STATE = {
musicList: [],
videoList: []
};
export function reducerX(state = INITIAL_STATE, action) {
switch (action.type) {
case 'FETCHING_MUSIC_SUCCESS':
return {
...state, musicList: action.payload, loading: false
};
case 'FETCHING_MUSIC_LOADING':
return { ...state, loading: true };
case 'FETCHING_MUSIC_FAILED':
return {
...state,
error: 'failed to fetch music',
loading: false
};
case 'FETCHING_VIDEO_SUCCESS':
return {
...state, videoList: action.payload, loading: false
};
case 'FETCHING_VIDEO_LOADING':
return { ...state, loading: true };
case 'FETCHING_VIDEO_FAILED':
return {
...state,
error: 'failed to fetch video',
loading: false
};
}
}
at actionX.js
export function fetchMusic() {
return dispatch => {
dispatch({
type: 'FETCHING_MUSIC_LOADING'
});
return API.get('/app/rest/music')
.then(({ data: music }) =>
dispatch({
type: 'FETCHING_MUSIC_SUCCESS',
payload: music
})
)
.catch(error => dispatch({ type: 'FETCHING_MUSIC_FAILED', payload: error }));
};
}
export function fetchVideo() {
return dispatch => {
dispatch({
type: 'FETCHING_VIDEO_LOADING'
});
return API.get('/app/rest/video')
.then(({ data: video }) =>
dispatch({
type: 'FETCHING_VIDEO_SUCCESS',
payload: video
})
)
.catch(error => dispatch({ type: 'FETCHING_VIDEO_FAILED', payload: error }));
};
}

How do you delay an async thunk action until after authentication session token has been stored?

I've built an API and I'm trying to fetch data (using axios) and render it into a component in my react-native app (node/express server). I'm using JWT to authenticate my users and react-navigation for screen routing.
I'm successfully authenticating and storing the session token into AsyncStorage. After login, the app navigates to the first screen where I want to show the list of data from my API.
The problem is that the GET request is being executed before the token is saved to AsyncStorage, so I'm getting a 401 unauthorized error.
The execution should be -
1) Authenticate user
2) Render component with fetched data
AHH! I can't figure it out. Please help? :(
I tried calling the action in lifecycle method componentDidMount, but no success with that.
Here's the parent component:
class PlansScreen extends Component {
static navigationOptions = ({ navigation }) => ({
tabBarLabel: 'Plans',
tabBarIcon: ({ tintColor }) => (
<Icon name="schedule" size={30} color={tintColor} />
)
});
render() {
return (
<View>
<ActivityList/>
</View>
);
}
}
export default PlansScreen;
Here's the child component I want to render the list of fetched data:
ignore the fact that I haven't fleshed out this entire component yet, I'm currently trying to just make the API request execute after the session token has saved
class ActivityList extends Component {
componentDidMount() {
this.props.fetchActivities();
}
render() {
// console.log(this.props);
return (
<View>
<FlatList
//My data will render here
/>
</View>
);
}
}
export default connect(null, {
fetchActivities,
})(ActivityList);
Here is the fetchActivities thunk action creator -
export const fetchActivities = () => {
return async (dispatch) => {
try {
dispatch({ type: FETCH_ACTIVITIES_INITIATE });
let token = await AsyncStorage.getItem('token');
let { data } = await axios.get(`${ROOT_URL}/activities`);
dispatch({
type: FETCH_ACTIVITIES_SUCCESS,
payload: data
});
console.log(data);
} catch(error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
// signupUserFail(dispatch);
};
};
};
Here's my activity list reducer -
import {
FETCH_ACTIVITIES_INITIATE,
FETCH_ACTIVITIES_SUCCESS
} from '../actions/types';
const INITIAL_STATE = {
activities: null,
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_ACTIVITIES_INITIATE:
return { ...state, loading: true };
case FETCH_ACTIVITIES_SUCCESS:
return {...state,
activities: action.payload,
loading: false
};
default:
return state;
}
};
Here's my authentication/login thunk action creator -
export const loginUser = ({ userEmail, userPassword, navigation }) => {
return async (dispatch) => {
try {
dispatch({ type: LOGIN_USER_INITIATE });
let { data } = await axios.post(`${ROOT_URL}/users/login`, {
userEmail, userPassword
});
AsyncStorage.setItem('token', data.token);
// AsyncStorage.getItem('token').then((res) => console.log(res));
dispatch({
type: LOGIN_USER_SUCCESS,
payload: data
});
navigation.navigate('Plans');
console.log(data);
// console.log(store.getState());
} catch(error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
// console.log(error.config);
loginUserFail(dispatch);
};
};
};
and here's my login/auth reducer -
const INITIAL_STATE = {
userName: '',
userEmail: '',
userPassword: '',
user: null,
loginError: '',
signupError: '',
loading: false
};
export default (state = INITIAL_STATE, action) => {
// console.log(action);
switch (action.type) {
case USERNAME_CHANGED:
return {...state, userName: action.payload};
case EMAIL_CHANGED:
return {...state, userEmail: action.payload};
case PASSWORD_CHANGED:
return {...state, userPassword: action.payload};
case LOGIN_USER_INITIATE:
return {...state, loading: true, loginError: '', signupError: ''};
case SIGNUP_USER_INITIATE:
return {...state, loading: true, loginError: '', signupError: ''};
case LOGIN_USER_SUCCESS:
return { ...state,
user: action.payload,
loginError: '',
signupError: '',
loading: false,
userPassword: '',
userEmail: ''
};
case LOGIN_USER_FAIL:
return { ...state, loginError: 'Authentication failed!', userPassword: '', loading: false };
case SIGNUP_USER_FAIL:
return { ...state, signupError: 'Signup failed!', userPassword: '', loading: false };
default:
return state;
}
};
this.props.fetchActivities is being called before the token is saved!
I've been trying to figure this out for hours!! Would really appreciate any help, even if it's just to lead me in the right direction.
Wow... I figured out what I did wrong and I feel so ridiculous for not realizing sooner x_x I forgot to add the auth header to the GET request and that's why it wasn't authenticating.
In the fetchActivities action creator, for me it should have been -
let { data } = await axios.get(`${ROOT_URL}/activities`, {
headers: { 'x-auth': `${token}` }
});
To anyone reading this in the future ----- make sure you added your auth header. Thanks to #Oblosys for his response, which helped me realize it had nothing to do with when the token was being saved.

Resources