I have the below method where I am updating store and after store updating, I am performing certain activities based on store values -
useEffect(()=>{
const asyncUpdateStore=async()=>{
await updateStore();
getDetails(storeValues) //this is api call made based on updated store values above
.then(()=>{...})
}
asyncUpdateStore();
},[applyChange]);
Upon execution of this code , I find that getDetails which is internally a axios call is not waiting for store values to be get updated within updateStore() method.
When useEffect is getting called second time , I find store is updated.
I want to wait execution of getDetails , till updateStore method finishes its execution.
I have also tried with -
useEffect(()=>{
const asyncUpdateStore=async()=>{
await updateStore();
}
asyncUpdateStore();
getDetails(storeValues) //this is api call made based on updated store values above
.then(()=>{...})
},[applyChange]);
Edit 1:
updateStore() method involves a dispatch call.
const updateStore=()=>{
const data:IData={
value1:valuestate1
value2:valuestate2
}
dispatch(updateData(data));
}
In redux, all dispatches are synchronous. Using await has no effect there. If updateData() is an asynchronous action, then you may need look at the documentation of the middleware you are using, to handle async actions (i.e redux-thunk, etc).
Usually, the middleware will provide 3 states ("pending", "success", "failed") that you can store in your redux store, and then read in your component. The logic flow could look like this.
//example route that would store the current status of an async response
const asyncState = useSelector(state => state.storeValues.status)
const storeValues = useSelector(state => state.storeValues.data)
useEffect(()=>{
//runs everytime applyChange changes
dispatch(updateData(data));
},[applyChange, dispatch, updateData]);
//runs everytime an async status changes, due to the api request done above
useEffect(()=>{
//success indicates the promise resolved.
if(asyncState === "success")
getDetails(storeValues) //this is api call made based on updated store values above.then(()=>{...})
},[asyncState]);
To understand how async patterns work in redux, or see more examples, you can check out redux's own tutorial and docs here. The have a conceptual diagram of state flow, as well as a ton of guides.
Note: dispatch actions should never be anything but synchronous, as reducers should not have side effects. Redux will throw errors and complain if an action is async, and you may see unexpected behavior in your store if async actions aren't handled outside reducers first.
Related
Suppose I have two dispatches I want to fire in order called
dispatch(action1())
dispatch(action2())
Action1 is an action creator created using
createAsyncThunk
method from redux/toolkit.
Therefore, it uses async... await in the process.
Action2 is a synchronous process.
I want to make
`dispatch(action2())` run only after `action1()` has been dispatched.
How can I achieve this?
I have tried doing
dispatch(action1(), dispatch(action2())
but I have found that dispatching inside of a reducer is an anti-pattern since reducers are pure.
See Unwrapping Result Actions
Thunks may return a value when dispatched. A common use case is to return a promise from the thunk, dispatch the thunk from a component, and then wait for the promise to resolve before doing additional work:
So you can do this:
dispatch(action1()).then(() => {
dispatch(action2());
})
A simple way might be to let action1 conditionally dispatch action2 where you need it:
const action1 = createAsyncThunk(
'actions/1',
async (args, thunkAPI) => {
const response = await someApi.fetchStuff(args.id);
if (args.condition) {
thunkAPI.dispatch(action2());
}
return response.data
}
);
This only works if you don't depend on the reducer having updated the state at that point (usually with this type of scenario it's about the asynchronous work having finished).
If you however also depend on the state update, try the dispatch(action1()).then(...) answer someone else gave.
Another option is to write a "classic" thunk (not using createAsyncThunk) that coordinates the sequence of action1 and action2.
I personally prefer the last option since bigger thunks can be composed nicely of many smaller thunks.
In case you're wondering, createAsyncThunk is not meant to dispatch another action when returning its result - which would have also solved your problem: https://github.com/reduxjs/redux-toolkit/issues/773
TL;DR: Is there some well-known solution out there using React/Redux for being able to offer a snappy and immediately responsive UI, while keeping an API/database up to date with changes that can gracefully handle failed API requests?
I'm looking to implement an application with a "card view" using https://github.com/atlassian/react-beautiful-dnd where a user can drag and drop cards to create groups. As a user creates, modifies, or breaks up groups, I'd like to make sure the API is kept up to date with the user's actions.
HOWEVER, I don't want to have to wait for an API response to set the state before updating the UI.
I've searched far and wide, but keep coming upon things such as https://redux.js.org/tutorials/fundamentals/part-6-async-logic which suggests that the response from the API should update the state.
For example:
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case 'todos/todoAdded': {
// Return a new todos state array with the new todo item at the end
return [...state, action.payload]
}
// omit other cases
default:
return state
}
}
As a general concept, this has always seemed odd to me, since it's the local application telling the API what needs to change; we obviously already have the data before the server even responds. This may not always be the case, such as creating a new object and wanting the server to dictate a new "unique id" of some sort, but it seems like there might be a way to just "fill in the blanks" once the server does response with any missing data. In the case of an UPDATE vs CREATE, there's nothing the server is telling us that we don't already know.
This may work fine for a small and lightweight application, but if I'm looking at API responses in the range of 500-750ms on average, the user experience is going to just be absolute garbage.
It's simple enough to create two actions, one that will handle updating the state and another to trigger the API call, but what happens if the API returns an error or a network request fails and we need to revert?
I tested how Trello implements this sort of thing by cutting my network connection and creating a new card. It eagerly creates the card immediately upon submission, and then removes the card once it realizes that it cannot update the server. This is the sort of behavior I'm looking for.
I looked into https://redux.js.org/recipes/implementing-undo-history, which offers a way to "rewind" state, but being able to implement this for my purposes would need to assume that subsequent API calls all resolve in the same order that they were called - which obviously may not be the case.
As of now, I'm resigning myself to the fact that I may need to just follow the established limited pattern, and lock the UI until the API request completes, but would love a better option if it exists within the world of React/Redux.
The approach you're talking about is called "optimistic" network handling -- assuming that the server will receive and accept what the client is doing. This works in cases where you don't need server-side validation to determine if you can, say, create or update an object. It's also equally easy to implement using React and Redux.
Normally, with React and Redux, the update flow is as follows:
The component dispatches an async action creator
The async action creator runs its side-effect (calling the server), and waits for the response.
The async action creator, with the result of the side-effect, dispatches an action to call the reducer
The reducer updates the state, and the component is re-rendered.
Some example code to illustrate (I'm pretending we're using redux-thunk here):
// ... in my-component.js:
export default () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(MyActions.UpdateData(someDataFromSomewhere));
});
return (<div />);
};
// ... in actions.js
export const UpdateData = async (data) => (dispatch, getStore) => {
const results = await myApi.postData(data);
dispatch(UpdateMyStore(results));
};
However, you can easily flip the order your asynchronous code runs in by simply not waiting for your asynchronous side effect to resolve. In practical terms, this means you don't wait for your API response. For example:
// ... in my-component.js:
export default () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(MyActions.UpdateData(someDataFromSomewhere));
});
return (<div />);
};
// ... in actions.js
export const UpdateData = async (data) => (dispatch, getStore) => {
// we're not waiting for the api response anymore,
// we just dispatch whatever data we want to our reducer
dispatch(UpdateMyStore(data));
myApi.postData(data);
};
One last thing though -- doing things this way, you will want to put some reconciliation mechanic in place, to make sure the client does know if the server calls fail, and that it retries or notifies the user, etc.
The key phrase here is "optimistic updates", which is a general pattern for updating the "local" state on the client immediately with a given change under the assumption that any API request will succeed. This pattern can be implemented regardless of what actual tool you're using to manage state on the client side.
It's up to you to define and implement what appropriate changes would be if the network request fails.
I know not to put side effects in reducers, and I know there are lots of great explanations about how to handle async actions. I have read them. I have a specific question I'm stumped on. Thanks!
I have state.largeObject which is an object with many entries. I have a reducer that does some complex logic and merges the result into state.largeObject like so:
export const myReducer = (state, { input }) => {
const largeObject = doSomethingComplex(input)
// other logic that uses largeObject
return {
...state,
largeObject: {
...state.largeObject,
...largeObject
}
}
}
I want to save the result of doSomethingComplex(input) to the server. Where do I put the side effect? EDIT: without duplicating doSomethingComplex which is still needed for other logic in the reducer.
I can't put it in myReducer or doSomethingComplex since they are pure.
I can't put it in Redux middleware, as that only has access to the action and the state as a whole. I don't want to call doSomethingComplex both in the reducer and in the middleware.
I don't want to move it into an action-creator, since that would force a lot of pure functionality into a thunk, making it harder to compose.
What am I missing? Thanks!
Middleware, thunks, and store subscription callbacks are all valid places to do that:
It's fine to have some saving logic as part of a thunk. Thunks are a type of middleware, and that's where side effects are supposed to live in general.
You could have a custom middleware that looks for the specific action type that results in this change, or checks to see if this chunk of state has changed, and makes a call to the server after the state has been updated
You could have a store subscribe callback that checks to see if this chunk of state has changed and makes a call to the server
Based on the way you phrased things, I'm not sure if you're looking to send only the result of that call to the server, or the result of doing {...state.largeObject, ...largeObject}.
The most suitable approach in my opinion would be moving the whole reducer logic (your doSomethingComplex function) to the server side.
So all what you'd have to do is dispatching the action and sending required arguments to the API. In case of a success response, you'd dispatch the success action, call myReducer and save the result in the store.
However, if you really want to keep this logic on the front side, you'd have to use a middleware - thunks or sagas (I prefer sagas).
// some pseudo code //
dispatch the action
- inside the middleware -
call doSomethingComplex() // called once and stored in some variable
dispatch action that will call the reducer and store the result
call API with the result
But still I'd recommend the first solution since that (the second) approach will work, but may break the proper data flow.
Edit: some final thoughts - if you really want to keep those calculations on the front-end side, consider following strategy:
dispatch the action
inside middleware (thunks or sagas) call doSomethingComplex function
call the API with the stuff that doSomethingComplex returned
return the stuff - that doSomethingComplex returned - from the API as the success response
call success action with the stuff and invoke the reducer that will save it in the store
This is how the proper data-flow can be kept alive.
I am building a recipe app and for some reason my API calls are sent 5 times since I see the data 5 times when in console.log it. This is an issue for me as the API blocks me from sending more than 5 calls a minute and it wont be very UX friendly if the end product has the same issue. Can anyone see where I am going wrong in the below code?
Id and app key is changed. please note that this is componentDidUpdate since I am running it when the state is changed -thus sending a fetch call
async componentDidUpdate() {
let response = await axios.get(
`https://api.edamam.com/search?q=${this.state
.searchTerm}&app_id=c5e1&app_key=946ddb0f02a86bd47b89433&to=20`
);
let data = response.data.hits.map((data) => ({
name: data.recipe.label,
src: data.recipe.image,
source: data.recipe.source,
url: data.recipe.url,
healthLabels: data.recipe.healthLabels,
dietLabels: data.recipe.dietLabels,
calories: data.recipe.calories,
totalWeight: data.recipe.totalWeight,
totalTime: data.recipe.totalTime,
totalNutrients: data.recipe.totalNutrients,
ingredients: data.recipe.ingredients
}));
this.setState({ recipeResults: data });
console.log(data);
}
The request depends on this.state.searchTerm. so if this.state.searchTerm is changed your component will make a request.
componentDidUpdate(prevProps, prevState) {
if (prevState.searchTerm !== this.state.searchTerm) {
axios.get(`https://api.edamam.com/search?q=${this.state.searchTerm}&app_id=c5e1&app_key=946ddb0f02a86bd47b89433&to=20`)
.then((response) => {
// process response
})
.catch(() => {
// process error
})
}
}
The problem is that you are fetching data in componentDidUpdate method of the component and using setState. It triggers component re-render and the code inside your componentDidUpdate executes again and again after every setState. Use componentDidMount for one time data fetching
If you want to do api call based on some piece of state being updated you should use either Redux or React context/hooks API. These libraries make it easy to subscribe to some state changes and execute an appropriate action. Ideally you should not do something like fetch in your componentDidUpdate as it can be called many times per second
Redux is really fast and time proven but it takes some time to setup and maintain the structure around it. So in your case I would look at useEffect() method of React hooks
I want to parse one excel sheet and before parsing I want some data from backend to map it.
So after clicking on Submit button, I want to trigger three actions one by one and store the response inside store. I am using redux-saga for this.
After the three action (api calls), I will call the parsing function and do the parsing and mapping using that response I will be fetching from store.
I have tried dispatching the three actions one by one. But as soon as it reaches the network client i.e axios instance to call api it becomes async and the next line gets executed.
onSubmit = () => {
/* I will set the loader on submit button till the api is called and all parsing of excel sheet is done. */
this.setState({
showLoader: true,
}, () => {
this.props.getData1(); //Will be saving it in store as data1
this.props.getData2(); //Will be saving it in store as data2
this.props.getData3(); //Will be saving it in store as data3
/* After this I want to call the parsing function to parse the excel sheet data and map accordingly */
parseExcelData(sheetData); //sheet data is the excel data
}
So I expected that when I will call the 'parseExcelData' function, the data from store i.e data1, data2,and data3 will be available in that function.
But all the api call happens after the sheet is being parsed.
I have done it using saga generator functions and is working fine. But I want to know how to deal with this situation with redux.
Putting an api call (or any other async operation) into a saga does not make that action synchronous, it is still async. Separately, redux-saga really does not support getting a result from an action -- you trigger a saga with an action, so when the saga completes, it has no way to return a result to the code that originally triggered it. (You can try to work around this by passing a callback along with the action that triggers the saga, and have the saga call the callback, but I wouldn't recommend this approach.)
I would recommend implementing this without redux-saga, using traditional action creators. The action creators would return promises that make the async api calls, and resolve with the result when they're finished. That might look something like this:
// action creator getData1, getData2, getData3
export const getData1 = () => {
return fetch(apiUrl).then(result => {
return result.json();
}).then(resultJson => {
// also fire an action to put it in the store here
// if other parts of your app need the data
return resultJson;
}).catch(err => {
console.error(err);
});
};
// react component
// assumes 1, 2, and 3 cannot be parallelized
// could also be written with .then instead of await
onSubmit = async () => {
this.setState({showLoader: true}, () => {
const result1 = await this.props.getData1();
const result2 = await this.props.getData2(result1);
const result3 = await this.props.getData3(result2);
});
}
You could have the action creators dispatch an action to put the data in the store instead of resolving the promise with the result. But that means you have to pick up the new data via the component's props, which probably means something in componentDidUpdate that checks if the new props are different from the old props, and if so, calls the next data-fetcher. IMO that approach is much more awkward.