React Redux wait for state change - reactjs

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))
});

Related

React Native Formik handleSubmit does not read return values from function

Good day!
Im having a weird experience using formik today,
I am currently working on an app that uses formik to handle forms and will do an API request inside the onSubmit() function of formik.
Every thing went well except when i use API request and wait for it's callback.
Somehow the things inside the function of onSubmit will work properly but the API callback value does not return unless i perform a UI Change in the app itself (like pressing random spots on my screen to trigger ui change).
Here is a look of my onSubmit function of formik
onSubmit={values => {
console.log("before")
let response = FunctionWithApiRequest(values);
console.log("after")
response.then((res) => {
console.log(res)
})
}}
and here is my function with api request inside
const FunctionWithApiRequest = (credentials) => {
return fetch(`${AppConfig.HOSTNAME}/v2/auth/signup`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials)
})
.then((response) => response.json())
.then((responseJson) => {
return responseJson
})
.catch((error) => {
console.log(error)
});
}
The return "responseJson" will only appear inside the onsubmit function when i perform a UI Change (like clicking random spots in my react native screen)
i was wondering what is the problem and what cause the bug.
Thank you for your response in advance.
Possibly you can do this in a separate function with await and async.For instance
async handleSubmit(){
let {credentials} = this.state
let data = await this.props.FunctionWithApiRequest(credentials)
this.setState({returnedData: data})
}
And now in your textInput/formik
onSubmit={()=>this.handleSubmit()}
I assume you have made the request api function in actions.file, not in the same file.Am i right?So what i have done is just put await before call.Which means next line will execute only when you have response returned.Comment down if you have any issue.
It was caused by the haul bundler, when you enable dugging mode.

React + axios + redux interceptors

I have an React + redux + axios app, white jwt authentication. So, i need to intercept every request to server and set header with token. The question is where I have to set this headers, or implement interceptors. (also, i need redux store in scope, to get tokens from store). My idea - implement it in the Index component. Is it right way?
I suggest you to set the header axios.defaults.headers.common.authorization. Take a look here Global axios defaults. If you need a working example, this public repo can help you out.
Why do you have to manually set the header. Can't you just store the JWT in a cookie and then the browser will forward it automatically for you. Just make sure you pass credentials: 'include' in your HTTP options.
create a redux-middleware to do these things.
Apart from acting like interceptor to add header token,
you also do request/response transformation.
Also,you can mention the next action to which you want to dispatch the result if you don't want to return the promise and result.
Middleware gives you a chance to get the store state and also fetch & dispatch other action
const apiInterceptor = store => next => action => {
if(action.type !== 'ajax') return next(action)
const state = store.getState();
state.token // get token here
//add this api check part in some other module.
if(action.api=='UPDATE_CLIENT')
{
method = 'post';
url = 'some url';
}
else
{
method = 'get';
url = 'other url';
}
fetch(url, {
method: method,
headers : 'add token here',
body: JSON.stringify(action.body())
})
.then(response => response.json())
.then(json => json)//either return result
//OR dispatch the result
.then(json => {
dispatch({type:action.next_action,payload : json})
})
}
Integrate the middleware with store.
import customMiddleware from './customMiddleware'
const store = createStore(
reducer,
applyMiddleware(customMiddleware)
)
I offer you to refer redux-thunk.
You must create api-wrapper-helper to inject to redux-thunk as extra argument, then access to api-wrapper-helper from redux actions.
api-wrapper-helper is a function that get url and method as argument and send request to api-server then return result. (you can set headers in this file)
For example you can see ApiClient.js of react-redux-universal-hot-example boilerplate.
This is an old post but its getting a few views, so something like this would work nicely and is easily testable.
apiclient.js
import axios from 'axios';
import { setRequestHeadersInterceptor } from './interceptors';
const apiClient = axios.create(
{
baseUrl: 'https://my.api.com',
timeout: 3000
});
apiClient.interceptors.request.use(setRequestHeadersInterceptor);
export default apiClient;
interceptors.js
export const setRequestHeadersInterceptor = config =>
{
// have each interceptor do one thing - Single Responsibility Principle
};
you should store your auth details in a httpOnly secure cookie, the transmission to/from the server will be automatic then
// Interceptor
axios.interceptors.response.use(function (response) {
// success case here
return response
}, function (error) {
// Global Error Handling here
// showErrorToaster(error['message']);
return Promise.reject(error.response.data)
})

React redux prev state ignores and rewrites by next state

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.

dispatch redux action only if a previous action has success

the situation is as follows: I need to call some API endpoints passing an auth token I previously requested. The problem is this token can expire, so each time I want to call an endpoint to retrieve data I need to ensure the token is valid and renew if necessary.
Currently I have an async action that renews the token and another set of actions that retrieves data from API.
My goal is to make some kind of aspect-programming/composition of both actions, like:
renewTokenIfNeeded = ... // action that check if token has expired and renew it
getData = ... // Retrieves data from API
realGetData = compose(renewTokenIfNeeded, getData);
The idea is realGetData action checks if token is expired, calling the renewTokenIfNeeded and only if this action success then call the getData.
Is there any pattern for this? Thanks.
I think you can create an action that returns a function instead of an object and use redux-thunk middleware. Actions that return a function are not pure and you can make more function calls and perform multiple dispatches within the action. So, it could look something like:
getData = () => async (dispatch, getState) => {
await renewTokenIfNeeded()
getData()
// Any dispatches you may want to do
}
Make sure you add the redux-thunk middleware to your store as mentioned in the documentation link.
You can use redux thunk or any other middleware (e.g redux saga) to solve the token issue. Redux thunk lets action creators return a function instead of an action.
export function checkTokenValidity(token) {
return (dispatch) => {
dispatch(TOKEN_CHECK_REQUEST());
return (AsyncAPICall)
.then((response) => {
dispatch(TOKEN_CHECK_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function getData(id) {
return (dispatch) => {
dispatch(GETDATA_REQUEST());
return (AsyncAPICall(id))
.then((response) => {
dispatch(GETDATA_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function checkTokenAndGetData(token,id) {
return (dispatch) => {
dispatch(checkTokenValidity(token)).then(() => {
dispatch(getData(id));
});
};
}
The problem with this approach is that code gets messy easily as you have to combine the checking of Token and an arbitrary API call every time.
This is a little better.
(2) (If you like to call it in your action creator for state tracking) Pass the token in the request, have a middleware to intercept the request and change the response to "Invalid Token/Expired Token", or the real data to have a clean and readable frontend code.
export function getData(token,id) {
return (dispatch) => {
dispatch(GETDATA_REQUEST());
return (AsyncAPICall(id))
.then((response) => {
//if response is EXPIRED token
dispatch(renewToken()); // renew token is another action creator
//else
dispatch(GETDATA_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
My suggestion, is to move your token validation in your backend.
(3)Always pass the token in the request, have a middleware to intercept the request, if it is expired, renew it in the backend code. Add an identifier to your response that it was expired and renewed it. Much simpler.

Data fetching with React Native + Redux not working

I am building my first React Native app and use Redux for the data flow inside my app.
I want to load some data from my Parse backend and display it on a ListView. My only issues at the moment is that for some reason, the request that I create using fetch() for some reason isn't actually fired. I went through the documentation and examples in the Redux docs and also read this really nice blog post. They essentially do what I am trying to achieve, but I don't know where my implementation differs from their code samples.
Here is what I have setup at the moment (shortened to show only relevant parts):
OverviewRootComponent.js
class OverviewRootComponent extends Component {
componentDidMount() {
const { dispatch } = this.props
dispatch( fetchOrganizations() )
}
}
Actions.js
export const fetchOrganizations = () => {
console.log('Actions - fetchOrganizations');
return (dispatch) => {
console.log('Actions - return promise');
return
fetch('https://api.parse.com/1/classes/Organization', {
method: 'GET',
headers: {
'X-Parse-Application-Id': 'xxx',
'X-Parse-REST-API-Key': 'xxx',
}
})
.then( (response) => {
console.log('fetchOrganizations - did receive response: ', response)
response.text()
})
.then( (responseText) => {
console.log('fetchOrganizations - received response, now dispatch: ', responseText);
dispatch( receiveOrganizations(responseText) )
})
.catch( (error) => {
console.warn(error)
})
}
}
When I am calling dispatch( fetchOrganizations() ) like this, I do see the logs until Actions - return promise, but it doesn't seem to actually to fire off the request. I'm not really sure how how I can further debug this or what resources to consult that help me solve this issue.
I'm assuming that Redux is expecting a Promise rather than a function.. Is that true?
If so, I think your return function may not be working.
You have a new line after your return, and it's possible JavaScript is (helpfully) inserting a semicolon there.
See here: Why doesn't a Javascript return statement work when the return value is on a new line?

Resources