I'm having a problem with controlling the execution of my fetch() functions. Particularly I want to avoid letting the user spam fetch() requests.
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
So my question is, when exactly does a fetch() already get executed?
If it matters, then my code looks roughly like this.
Parent action:
{
return (dispatch) => {
if (mode === 'MY') {
dispatch(myAction(...);
}
dispatch(someOtherAction(...));
}
}
My action:
{
type: 'TYPE',
promise: post(url, payload)
}
My post method:
{
console.log('sending POST');
return fetch(url, {
//fetch info
});
}
My middleware:
{
return next => action => {
const { promise, //other fields } = action;
//Already a promise here.
if (!promise) {
return next(action);
}
return promise.then(
//Processing data
);
};
}
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
To solve this problem, I would do it as a check inside the action. So when you fire the fetch request, dispatch an action that a fetch is currently executing, update the state to say fetching = true. Then in any other action that needs to use fetch, check against that state and return nothing if it's true.
To answer your comment:
When you call an action creator, that is called immediately of course. When you call the dispatch, that will then execute the function (if using redux-thunk). Now, since redux-thunk is a middleware, it executes depending on order that you attached the middleware. So if you put your own middleware in the setup before redux thunk, it will execute before it.
const store = createStore(
combineReducers(reducers),
applyMiddleware(yourMiddleware, ReduxThunk)
);
Otherwise you have the understanding correct. An action fires immediately after calling dispatch, it'll go through the middleware in order, and then pass the action info to your reducers.
Related
Using the store method dispatch from the parameter provided by Redux Thunk middleware does not trigger the reducer. While using next() works properly as it triggers the reducer. Why is this happening?
middlerware
export default function createSlimAsyncMiddleware({
dispatch,
getState
}) {
return next => action => {
const {
types,
callAPI,
shouldCallAPI = () => true,
} = action;
if (!actionIsValid(action)) next(action);
if (shouldCallAPI(getState())) {
return Promise.resolve(getState());
}
const [pendingType, successType, errorType] = types;
dispatch({
type: pendingType
});
return callAPI()
.then(response => {
dispatch({ // Does not work, use next()
type: successType,
payload: response,
});
console.log('call resolved with type', successType)
return Promise.resolve(getState());
})
.catch(error => {
dispatch({ // Does not work, use next()
type: errorType,
payload: error,
});
return Promise.reject(error);
});
};
}
store
const store = createStore(
appReducer,
composeWithDevTools(applyMiddleware(
thunk,
createSlimAsyncMiddleware,
routerMiddleware(history)
))
)
Regarding this response https://stackoverflow.com/a/36160623/4428183 the dispatch should also work.
This is stated in the linked response you included, but calling dispatch() will create a new action, which then goes through the entire middleware chain from the beginning. In your case, this includes the middleware you're troubleshooting. From what I can see, the only time you call next() is in the case that an incoming action is deemed invalid. Otherwise, the subsequent API call results in dispatch() being called again whether the call succeeds or fails, and so the action never gets to the reducer because it's constantly being set at the beginning of your middleware chain and never gets to move along via next().
When you say this code doesn't work, what is the specific behavior? Does your app hang? Does it crash? Because this scenario essentially sets up a recursive function with no base case, I'd bet that you're seeing 'maximum call stack exceeded' sorts of errors.
I guess I'd ask why you need to use dispatch() for request results as opposed to sending them along using next(), or why you haven't set this up in a way that sets a conditional that uses the result of the previous call to determine whether the API gets called again.
In a component I have a button that onClick dispatches a deleteQuestion action that sends a fetch backend delete request, and when the response is received is supposed to call another action to update the Redux store.
However, since it's an onClick event, the deleteQuestion thunk function does not work like a traditional dispatch request made from ComponentWillMount and instead returns an anonymous function with a dispatch parameter that never is called. Therefore, I'm required to call the dispatch twice simultaneously in the onClick method like so:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
deleteQuestion(questionId, history)(deleteQuestion); //calling method twice
}
While this approach is effective for trigging the delete request to the Rails backend, when I receive the response, the second dispatch function that I have embedded in the deleteQuestion action -- dispatch(removeQuestion(questionId)) -- won't trigger to update the Redux store. I've tried placing multiple debuggers in the store and checking console and terminal for errors, but nothing occurs.
I've read through the Redux docs and other resources online and from what I've been able to find they all say it should be possible to include a second dispatch call in a .then request. While it's possible to do this in get, post, and patch requests, I can't figure out why it won't work in a delete request.
The thunk call I make is:
export function deleteQuestion(questionId, routerHistory) {
return (dispatch) => {
fetch(`${API_URL}/questions/${questionId}`, {
method: 'DELETE',
}).then(res => {
dispatch(removeQuestion(questionId))
})
}
}
And the github is:
https://github.com/jwolfe890/react_project1/blob/master/stumped-app-client/src/actions/questions.js
I'd really appreciate any insight, as I've been trying to get passed this for two days now!
You are calling the action deleteQuestion directly instead of having your store dispatch the delete question action for you. You should instead call the deleteQuestion from your props that is already mapped to dispatch:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
this.props.deleteQuestion(questionId, history);
}
If you pass in an object as mapDispatchToProps each element is dispatch call. In other words your mapDispatchToProps is equivalent to:
(dispatch) => ({
deleteQuestion: (...params) => dispatch(deleteQuestion(...params))
})
I am new to react redux. I want to add a loading text when the user pressed the search button and dismiss the text when data comes back or the action completed.
In a normal async function, I can just toggle the isLoading flag before and after the call back.
However, in my app, I am dispatching an action that returns a 'type' and 'payload' that is a promise. The middleware redux-promise then 'automatically' converts that promise payload and send it to the reducer.
In other words, the middleware does the .then action for the promise behind the scene and gives my reducer the correct data.
In this case, I am not sure how I can add a loading text to my view because as soon as I call this.props.getIdByName(this.state.value), I do not know when the data comes back.
The logical place for me would be inside the reducer since that is when the data comes back. However, I belive that would be a bad way because reducers should not perform such task?
Inside my component, I have the following function for my submit
handleSubmit(e) {
e.preventDefault();
this.props.getIdByName(this.state.value);
}
Inside my actions/index.js file, I have the following action generator
export function getIdByName(name) {
const FIELD = '/characters'
let encodedName = encodeURIComponent(name.trim());
let searchUrl = ROOT_URL + FIELD + '?ts=' + TS + '&apikey=' + PUBLIC_KEY + '&hash=' + HASH + '&name=' + encodedName;
const request = axios.get(searchUrl)
return {
type: GET_ID_BY_NAME,
payload: request
}
}
Inside my reducers/reducers.jsx
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ID_BY_NAME:
console.log(action.payload.data.data.results[0].id); <-- here the data comes back correctly because reduer called the promise and gave back the data for me
return {...state, id: action.payload.data.data.results[0].id};
default:
return state;
}
}
And in my main index.js file, I have the store created with the following middleware
const createStoreWithMiddleware = applyMiddleware(
promise,
thunk
)(createStore);
When you dispatch an action with a Promise as its payload while using redux-promise, the redux-promise middleware will catch the action, wait until the Promise resolves or rejects, and then redispatch the action, now with the result of the Promise as its payload. This means that you still get to handle your action, and also that as soon as you handle it you know it's done. So you're right in that the reducer is the right place to handle this.
However, you are also right in that reducers should not have side-effects. They should only build new state. The answer, then, is to make it possible to show and hide the loading indicator by changing your app's state in Redux :)
We can add a flag called isLoading to our Redux store:
const reducers = {
isLoading(state = false, action) {
switch (action.type) {
case 'START_LOADING':
return true;
case 'GET_ID_BY_NAME':
return false;
default:
return state;
}
},
// ... add the reducers for the rest of your state here
}
export default combineReducers(reducers);
Whenever you are going to call getIdByName, dispatch an action with type 'START_LOADING' before calling it. When getIdByName gets redispatched by redux-promise, you'll know the loading is done and the isLoading flag will be set back to false.
Now we have a flag that indicates whether we are loading data in our Redux store. Using that flag, you can conditionally render your loading indicator. :)
I am sure Pedro's answer should get you started but I recently did the very exact loader/pre-loader in my app.
The simplest way to do it is.
Create a new key in the state object in one of your reducers and call it showLoader: false.
In the same container as before(the one with the button), create a mapStateToProps and get that state property showLoader.
Inside the container that holds the button you are trying to trigger the loader with, add an onClick event which calls an action, say displayLoader. In the same function also set the this.props.showLoader to true
Create a displayLoader action write something like this:
function displayLoader() {
return {
type: DISPLAY_LOADER,
payload: {}
}
}
In the reducers, catch this action and set the showLoader back to false when your desired payload is received.
Hope it helps!
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.
Hi I'm new to the react/redux development environment so I appreciate any help.
I'm trying to make 2 API calls asynchronously when componentDidMount by calling dispatch(fetchAllPositions(selectedUserDivision)) in my app.js
I found a suggested method in this post and the fetchAllPositions function wraps two action functions together in a Promise.all
export function fetchAllPositions(division) {
return dispatch => Promise.all([
dispatch(fetchUserPositionsIfNeeded(division)),
dispatch(fetchDefaultPositionsIfNeeded(division))
])
.then(console.log("fetched both"))
}
The two action functions are nearly identical, they just call a slightly different API url. One of them looks like the follows, where the shouldFetchUserPosition is just a pure function that returns a boolean:
function fetchUserPositions(division) {
return dispatch => {
const url = apiOptions.server + `/api/user/position?division=${division}`
dispatch(requestUserPositions(division))
return fetch(url, options)
.then(response => response.json())
.then(json => dispatch(receiveUserPositions(division,json)),
err => console.log(err))
}
}
export function fetchUserPositionsIfNeeded(division) {
return (dispatch, getState) => {
if (shouldFetchUserPositions(getState(), division)) {
return dispatch(fetchUserPositions(division))
} else {
return Promise.resolve()
}
}
}
The logic is that for an update, a REQUEST... action is sent while pulling data, then a RECEIVE... action is sent when data is ready to be updated into the new state.
The trouble is that the Promise.all should wait for REQUEST1 REQUEST2 RECEIVE1 RECEIVE2 to all come in before doing the .then(console.log("fetched both"))
Right now, it's does the .then after first 2 REQUEST are finished, not waiting for the 2 RECEIVE to come in.
I suspect it's the nested nature of the requestUserPositions() within the function that wraps fetch()
The REQUEST action is a simple function, and in the reducer it just flips an isFetching property to true:
function requestUserPositions(division) {
return {
type: REQUEST_USER_POSITIONS,
division
}
}
Sorry for this long issue but I'd appreciate any suggestions.
This is a careless mistake
Turns out that when the .then() is wrapped inside a dispatch it gets executed simultaneously as the Promise.all() is being carried out.
The intended dispatch order was created by tagging the then(()=>console.log to the last dispatch dispatch(fetchAllPositions(selectedUserDivision)) done from ComponentDidMount