This question already has answers here:
Is store.dispatch in Redux synchronous or asynchronous
(2 answers)
Closed 5 years ago.
si it guaranteed that by time store.dispatch returns, the state has already changed? and if thats case, then why not return newState as a result of dispatch call?
store.dispatch( action1() );
store.dispatch( action2() );
an example I will login user in action1, then i want to fire another action that will use the LOGEDIN user information to change the state further more. so i want to make sure that action2 will not fire unless action1 has already changed the state successfully.
so, by time store.dispatch returns, is this garanteed that the state has already changed ?
example reducer:
function reducer(state, action){
// please ignore that i will mutate state, just for sake of simplicity of example.
if(action.type==='ACTION1'){
state.user_id = action.payload;
return state;
}
if(action.type==='ACTION1'){
state.value2 = state.user_id * action.whatEver;
return state;
}
return state;
}
my current protection is i use React.component to monitor changes to user_id then i fire action2, but if Redux Actions are synchronous then i can fire action2 directly after action1 and just reduce my boilerplate.
Yes, by default dispatch() is 100% synchronous. Middleware can intercept actions and potentially delay them or modify behavior in some other asynchronous way. But,other than that, by the time dispatch returns, it is guaranteed that the root reducer has finished running, the current state value has been swapped out, and the subscribers have been notified.
Related
I currently have this piece of code:
const handleClick = async () => {
dispatch(resetFilters());
if (router.pathname !== '/') {
await router.push('/');
}
};
Where resetFilters() is a function to reset all the state in a slice.
My problem is that wherever I place this function (before or after the reroute), it will cause data to be fetched twice (since what data is fetched depends on the state).
If I place it before, I fetch data based on the reset state on the page I'm rerouting away from (which I won't use)
If I place it after, I fetch data based on the old state on the page I'm rerouting to, which then has to be fetched again with the reset state.
I saw that react-router-redux has a LOCATION_CHANGE action which seems to solve my problem.
Is there an equivalent version for next-router?
I.e. I need something which allows me to update redux state and redirect with next-router in an atomic step.
If I have code like this,
dispatch({
type: actionTypes.ABCD,
...newInfo,
});
const state = getState();
Can I rely 100% on state being the latest state? Or is dispatch asynchronous and I have no guarantee on whether or not the state has updated?
Yes. A call to dispatch(), by itself, is 100% synchronous. By the time dispatch() returns, the root reducer has been run, the state value has been updated, and all subscribers have been notified. So yes, a call to getState() immediately after dispatch() will return the latest value.
The caveat to that is that any middleware can intercept, delay, or modify a dispatched action. So, the final answer is dependent on what middleware you have installed and how they are configured.
getState() does retrieve the latest value, but if you are not mutating your state (which you shouldn't be, for example with the Immer library), you have to be careful if you set it to a 'state' variable inside your action creator.
For example:
let state = getState();
dispatch({
type: actionTypes.someStateChange,
newInfo1: newInfo
});
dispatch({
type: actionTypes.someOtherAction,
infoDependentOnNewInfo1: state.newInfo1.property
});
This will not behave as expected because state as define above will have the old state, since the first dispatch has created a new immutable state that is not referenced by the 'state' variable.
As a scenario, the user can click a button to create a list of timestamps that shows the corresponding times when the clicks are made. User can also click on an item on the list to remove an item.
In terms of the store, there's a counter state that keeps track of how many times the button has been clicked, and then there's another state that keeps track of a list of timestamps. And each item on list state has an id field that derive from the counter state. So one part of the store depends on another part.
As an attempt, I dispatch one action, and both reducers handle the same action, and it works fine, but only that it's not DRY. Before dispatching, I have to add 1 to the counter state in order to get the new id which I use as the action payload, after dispatching, I add 1 to the counter state again to return the new counter state. That's repeating myself.
What's the general standard way of handling a problem of this nature?
The general simple way is to use thunks. You need to setup a middleware, check out the docs:
https://github.com/gaearon/redux-thunk
This allows you to dispatch a function instead of a simple action. Within that function, you can access state and dispatch as many times as you want.
In your scenario, you would first increment the counter, then retrieve the length to get your new id, and then dispatch another action to create a timestamp.
Some imaginary code for your action creators:
// basic action creators to be handled in your reducers
function incrementCounter(){
return { type: 'INCREMENT'}
}
function createTimestamp(id){
return { type: 'CREATE_TS', id }
}
// this is the thunk
function incrementAndTimestamp(){
return (dispatch, getState) => {
// increment the counter
dispatch(incrementCounter())
// generate an "id" from the resulting state
const newId = getState().counter.length
// and use that id to further update your state
dispatch(createTimestamp(newId))
}
}
You will need to handle those 2 different actions in your reducers and you have now two separate pieces of code. The thunk is the glue that dispatches, gets the data from one part, and uses it to affect the other part.
Let's pretend I have a long-running function working on computing my new state.
Meanwhile another action comes in and changes the state while the first one did not finish and is working on stuff.
If I am imagining things correctly there is no actions queue and the state might be resolved in some unpredictable manner.
Should I be worried about this at all?
I don't mean real threads, just a concept for the lack of better wording. Actions are asynchronous and state keys are being accessed by reference.
I was concerned about the same thing so I just did some digging. It looks like two threads concurrently calling dispatch() (if it were possible) could raise an exception. But it shouldn't be possible and that error message points to a particular, different cause. The "actions queue" is in the browser's own event loop. That event loop runs async/interaction callbacks (from which we call dispatch()) one-at-a-time.
That's the responsibility of your own action creators and your own reducers, and heavily related to how you structure your actions and reducers conceptually. The Redux FAQ question on structuring "business logic" is very relevant here:Redux FAQ
Thunk action creators have access to getState, so it's very common to have a thunk check the current state and only dispatch under certain conditions, such as this example:
// An example of conditional dispatching based on state
const MAX_TODOS = 5;
function addTodosIfAllowed(todoText) {
return (dispatch, getState) => {
const state = getState();
if(state.todos.length < MAX_TODOS) {
dispatch({type : "ADD_TODO", text : todoText});
}
}
}
Your reducer can also have sanity checks as well:
function todosReducer(state, action) {
switch(action.type) {
case "ADD_TODO": {
if(state.todos.length >= state.maxTodos) {
return state;
}
return {
...state,
todos : state.todos.concat(action.newTodo)
}
}
default : return state;
}
}
Personally, I don't like to have my reducers just blindly merge in whatever data's in the action, unless it's very small (like, say, the name of the currently selected tab or something). I prefer to have a reasonable amount of logic in my action creator to set up the action, a minimal-ish amount of data included in the action itself, and a sufficiently smart reducer to do the work based on that action.
In this github redux example, a dispatch of the event ADD_TODO is used to add a task. During the debugging, I found out that adding a task causes both the reducers todos and visibilityFilter being called.
How can I call just the todos reducer and not visibilityFilter reducer when I add a task. Also the visibilityFilter reducer if I sent an event of type SET_VISIBILITY_FILTER.
The combineReducers utility intentionally calls all attached reducer functions for every action, and gives them a chance to respond. This is because the suggested Redux reducer structure is "reducer composition", where many mostly-independent reducer functions can be combined into one structure, and many reducer functions could potentially respond to a single action and update their own slice of state.
As mentioned in other answers, combineReducers calls every reducer whenever a dispatch is called. You can avoid the other values changing, by making the default case equal to the state parameter passed in, so essentially they are reassigned their current value.
For example:
const individualReducer = (state = "initialState", action) => {
switch(action.type)
{
case "ACTION_TYPE":
return action.payload;
default:
return state;
}
}
export default individualReducer;