React redux prev state ignores and rewrites by next state - reactjs

Below an example what's goes wrong in my app that I can't figure out how to fix it and why it's actually happening.
I made a simple API call to fetch some data
dispatch(isLoadingData(true));
api({
method,
url,
headers: { Authorization: `JWT ${APP.token}` }
})
.then(async (response) => {
return resolve(dispatch(await updateData(response)));
}).catch(reject);
}).catch(async (err) => {
dispatch(isError(err));
dispatch(await isLoadingData(false));
throw err;
});
if API response has 401 status code that it initiates logout process by calling the next action.
case LOGOUT_USER: {
Actions.main({ type: 'reset' });
return {
...INITIAL_STATE,
};
}
that restores the user reducer to
{ user: '', token: '' };
(It calls before Promise returns a response in the upper function). So the sequence looks like this
1) isLoadingData(true)
2) logoutUser() (this clears user data)
3) isError(err) (this again has user and token)
4) isLoadingData(false)
As a result, the user can't be logging out.

just add another
.then(response => dispatchActionAfterActualLogoutWasMade())
so that you dispatch the action after the initial logout request was completed
that will send an action to reducer to clear the app state after you already received the response from the server.

Related

Send Rest call in a loop

I have a Rest API that in order to get the end data AKA:ENDDATA I need to send few requests (depends how deep). So when sending the first request the return response will have in the body the nextUri and so on…
Basically I need to execute a list of Rest calls based on the nextUri till I reach to the end ENDDATA.
Is there a preferred way to do this with React & Redux\redux-thunk.
export const find = () => dispatch => {
dispatch({ type: FIND_CUSTOMER_START });
axios({
method: 'post',
url: '/v1/...',
headers: { 'X': 'XXX1' },
auth: {
username: 'XXX1',
password: 'XXX'
},
data: 'SELECT * FROM .....'
}).then(function (response) {
// Need to find the nextUri ... before update teh state
dispatch({
type: SUCCESS,
payload: response.data
});
}).catch(function (error) {
dispatch({ type: ERROR, payload: error });
});
}
Thank you
Define your api call in a redux action
Inside a component lifecycle method you can call this action. As well as map the state from the store to your component.
When the action updates the state it will cause a re-render and call the lifecycle method. In the lifecycle method you can check to see if ENDDATA is true and if not call the action again.
Inside render you can have the same conditional as in the lifecycle method and have it return a spinner to indicate loading.

Axios promise resolving on pre-flight request response which makes associated GET execute out of order with rest of app

Good day,
I am working on a React app that makes use of React-Redux (with Thunk) and Axios.
I have an action that I dispatch which makes an authenticated API call. Due to the fact that I have Authorization headers on a cross-origin request, there is a pre-flight request.
The problem that I have is that Axios seems to be running the .then() code once the pre-flight request returns rather than when the associated GET request returns. This results in the Reducer function updating state before the results of the API GET request return.
I have added some console.logs to give more details to illustrate the problem. As you can see the pre-flight request is sent in the first entry. The .then executes one the pre-flight request returns with 200. The action then fires off and the reducer updates the state. My app responds by re-rendering the container that was connected to Redux. The child components also then update. Then the GET request completes and returns with a 200. And at this point nothing further happens because the reducer was already updated in the prior .then() mentioned above.
The action code is shown below. I have not pasted all the other code in as there are a number of files and they are relatively big. If needed I can include those too.
export const updatePlotDataInit = (datasetName, tableName, xFieldName,
yFieldName, chartId, chartType, plotId, newDomainStartDbIndex, newDomainEndDbIndex) => {
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [start of function]');
return dispatch => {
dispatch(updatePlotDataStart());
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [just before api request]');
instance.get( // custom axios instance with extra auth header used here
`/v1/datasets/${datasetName}/tables/${tableName}/fields/data?xField=${xFieldName}&yField=${yFieldName}&chartType=${chartType}&domainStart=${newDomainStartDbIndex}&domainEnd=${newDomainEndDbIndex}`
)
.then(response => {
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [in .then before updatePlotDataSuccess]');
// dispatch redux action for success case
const currentLevel = response.data.metaData.current_level
const data = response.data.queryData.data //schema is available too
//datasetId, tableId, xFieldId, xField, yFieldId, yField, chartId, plotIdVal, currentLevel, data
dispatch( updatePlotDataSuccess( plotId, currentLevel, data ) );
// console.log(response);
console.log('[actions/plot.js.js] - [updatePlotDataInit] - [in .then after updatePlotDataSuccess]')
})
.catch(error => {
console.log(error);
// dispatch redux action for failure case
dispatch(updatePlotDataFail(error));
})
}
};
I am not entirely sure but it seems that Axios is seeing the successful pre-flight response as suitable to resolve the promsie and hence the .then gets executed.
Does this seem to be the case? And if so how would I force Axios to wait for the GET/POST/PUT/etc to succeed before resolving the promise?
Any help is appreciated.
I know it is for long time ago, but I think it could be useful for others who find this issue similar to their problem, with no answer...
for me it was just because of a careless coding :D,
here is my response inceptor, I missed "return" before Promise.resolve(axios(originalRequest));
I solved it by adding return :
AxiosInstance.interceptors.response.use(
(response) => {
return response;
},
function (error) {
const originalRequest = error.config;
let refreshToken = localStorage.getItem("refreshToken");
if (
refreshToken &&
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
return axios
.post(apiUrl + `auth/refreshtoken`, { refreshToken: refreshToken })
.then((res) => {
if (res.status === 200) {
localStorage.setItem("accessToken", res.data.accessToken);
console.log("Access token refreshed!" + res.data.accessToken);
originalRequest.headers.Authorization = 'Bearer ' + res.data.accessToken;
//*************** I just return promise.resolve *****************//
return Promise.resolve(axios(originalRequest));
}
}).catch((error) => {
console.log(error);
});
}
return Promise.reject(error);
}

React Redux wait for state change

I Want to extract all my server call functions (HTTP GET, POST, ...) into one helper function. The helper function will be called from different react components. It will check if the JWT Token is expired or not (client side) and if expired will ask user to relogin and get a new token before sending the request to the server.
Problem: When the helper function finds out the Token is expired it will dispatch an action to show the Login Dialog however it will continue the code calling the fetch. I need to wait for the Login Dialog to change the LoggedIn state and token before calling the server however it doesn't seem to be possible to watch for this state change. (One idea is returning a promise from Login dialog however I can't understand how to return a promise and where from!)
I appreciate that all the above can be very abstract and difficult to follow so have created a full code example showing what I need.
Sample Code
*PS : If the code sandbox fails please refresh it. They seem to be having some race issue with some of the plugins!
Is this what you are looking for?
componentDidUpdate(prevProps) {
if (!prevProps.loggedIn && this.props.loggedIn) {
// User just logged in
}
}
I am not a specialist of thunk yet, but what i can say is that you serverCall function must return a function with a dispatch parameter (see examples here)
You must dispatch an action in this sub function (in fact, call an action creator which will put the data in the application state.
You don't have to make an explicit promise because fetch return already a promise.
I will try something like :
export const serverCall = (
url,
method,
body = undefined,
successMessage = undefined
) => {
// your code
return function (dispatch) {
return fetch(url, {
method: method,
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
...(body && { body: JSON.stringify(body) })
}).then(response => response.JSON())
.then(response =>
if (response.ok) {
if (successMessage) {
console.log(successMessage);
}
dispatch(fetchData(response))
} else {
index.js
<Button
onClick={() =>
this.props.serverCall(
"https://jsonplaceholder.typicode.com/users",
"GET"
)
>
The state is to be removed here if you use Redux. All is taken from props via mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
onLogin: (username, password) => dispatch(login(username, password)),
ToggleIsLoginDialogOpen: IsLoginDialogOpen =>
dispatch(toggleIsLoginDialogOpen(IsLoginDialogOpen)),
serverCall: (url, method) => dispatch(serverCall(url, method))
});

How to set state the data in componentDidMount method from the epic redux-observable

I am new to redux-observable. I have an epic function which request an api call and emmit a new success action. What is the clean way to get the data in my react view? In my componentDidmount method I call the getUsers redux action. I wanted to set the state in componentDidmount but It will run only once and will not wait the promise epic.
const getUsersEpic = action$ =>
action$.ofType(GET_USERS)
.mergeMap( action =>
Observable.fromPromise(
axios({
method: 'get',
url: API_USERS,
headers : getHeaders().toObject()
})
)
)
.flatMap((response) => {
if(response.status == 200) {
return [{
type: GET_USERS_SUCCESS,
data: response.data,
status: response.status
}]
}
else {
return [{
type: GET_USERS_ERROR,
message: 'error',
status: response.status
}]
}
})
.catch( error => {
// console.log('error', error)
return setResponseError(error, GET_USERS_ERROR);
});
You dispatch an action in componentDidMount() that is intercepted by redux-observable and used to make ajax call. As a result, you get GET_USERS_SUCCESS or GET_USERS_ERROR action dispatched to redux at some point in the future. Generally, there is no way to await these actions in components. And I think it's a good limitation because this restricts async handling in the component which can in many ways introduce bugs.
What you need to do is set default state in reducer which is later updated by request response and pass it down as a props to your component.
Also, check out this answer by one of the redux-observable authors.
By the way is there any reason why you Axios instead AjaxObservable

How to implement login authentication using react-redux?

After a bit of research, JWT is commonly used for login authentication because of its compact nature and easiness to parse. I have settled on using JWT. However, my question is on how to embed this in my redux paradigm. Assuming we have a sign up form, when a user fills in his or her credentials and clicks a submit button, this will invoke an action to create an action to create a JWT. Now, this action goes to the back-end of my application and the back-end of my application calls the JWT API? So this action is an asynchronous/rpc call? Also, how does routing happen exactly? I have used react-router before, but using a boilerplate. I am building this web app from scratch and so I am a bit confused on where to deal with the routing and where do I pass this token exactly that I obtain from the server the first time? Is the token used every time a user does a request? How does the client know about this token every time it does the request so that it would keep a user authenticated?
When a user submits his credentials (email/password) your backend authenticates that for the first time and only this time does the backend use these credentials. On authentication your backend will create a JWT with some of the user information, usually just the user ID. There are plenty of JWT Libraries and even jwt-decode for javascript to do this. The backend will respond with this JWT where the front-end will save it (ie, localStorage.setItem('authToken', jwt)) for every subsequent request.
The user will send a request with the JWT in the request header under the Authorization key. Something like:
function buildHeaders() {
const token = localStorage.getItem('authToken')
return {
"Accept": "application/json",
"Content-Type": "application/json"
"Authorization": `${token}`
}
}
Your backend will now decode and authenticate the JWT. If it's a valid JWT the request continues, if not it's rejected.
Now with React-Router you can protect authenticated routes with the onEnter function. The function you provide does any necessary checks (check localStorage for JWT and if a current user). Typically I've done this:
const _ensureAuthenticated = (nextState, replace) => {
const { dispatch } = store
const { session } = store.getState()
const { currentUser } = session
const token = localStorage.getItem("phoenixAuthToken")
if (!currentUser && token) { // if no user but token exist, still verify
dispatch(Actions.currentUser())
} else if (!token) { // if no token at all redirect to sign-in
replace({
pathname: "/sign-in",
state: { nextPathname: nextState.location.pathname}
})
}
}
You can use this function in any route like so:
<Route path="/secret-path" onEnter={_ensureAuthenticated} />
Check out jwt.io for more information on JWT's and the react-router auth-flow example for more information on authentication with react-router.
I personally use Redux saga for async API calls, and I'll show You the flow I've been using for JWT authorization:
Dispatch LOG_IN action with username and password
In your saga You dispatch LOGGING_IN_PROGRESS action to show e.x. spinner
Make API call
Retrieved token save e.x. in localstorage
Dispatch LOG_IN_SUCCESS or LOG_IN_FAILED to inform application what response did You get
Now, I always used a separate function to handle all my requests, which looks like this:
import request from 'axios';
import {get} from './persist'; // function to get something from localstorage
export const GET = 'GET';
export const POST = 'POST';
export const PUT = 'PUT';
export const DELETE = 'DELETE';
const service = (requestType, url, data = {}, config = {}) => {
request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';
switch (requestType) {
case GET: {
return request.get(url, data, config);
}
case POST: {
return request.post(url, data, config);
}
case PUT: {
return request.put(url, data, config);
}
case DELETE: {
return request.delete(url, data, config);
}
default: {
throw new TypeError('No valid request type provided');
}
}
};
export default service;
Thanks to this service, I can easily set request data for every API call from my app (can be setting locale also).
The most interesting part of it should be this line:
request.defaults.headers.common.Authorization = get('token') ? `Token ${get('token')}` : '';`
It sets JWT token on every request or leave the field blank.
If the Token is outdated or is invalid, Your backend API should return a response with 401 status code on any API call. Then, in the saga catch block, you can handle this error any way You want.
I recently had to implement registration and login with React & Redux as well.
Below are a few of the main snippets that implement the login functionality and setting of the http auth header.
This is my login async action creator function:
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
history.push('/');
},
error => {
dispatch(failure(error));
dispatch(alertActions.error(error));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
This is the login function of the user service that handles the api call:
function login(username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
};
return fetch('/users/authenticate', requestOptions)
.then(response => {
if (!response.ok) {
return Promise.reject(response.statusText);
}
return response.json();
})
.then(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
And this is a helper function used to set the Authorization header for http requests:
export function authHeader() {
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token };
} else {
return {};
}
}
For the full example and working demo you can go to this blog post

Resources