My assumption from working with redux is that dispatching actions is a synchronous task.
fire action 1 - > store updated
fire action 2 -> store updated
In a project I'm currently working on, I have a product customizer, that allows some user selection, they can place multiple orders, but if they're only ordering their current selection and select "purchase", I fire "addOrder", adding their selection to the orders array, and then the "purchase" action, which is a thunk submitting the orders stored in redux to my cart API.
I've expected that I would be able to rely on the store being in a consistent state, reliably after each action, and so when that second action fires it would have the state, as it is, after the first regular action fired before it, but no dice.
Are my expectations and understanding of redux here incorrect?
If so, Is redux thunk acting outside the normal dispatch in some way?
in my connected component I dispatch each action:
//.... inside component
purchase = () => {
this.props.addOrder(); // regular action
this.props.purchase(); // thunk
};
// ... rest of component
Yes, dispatching is always 100% synchronous, unless altered by a middleware. And yes, by default, you can call getState() again after a dispatch to get the updated state:
function checkStateAfterDispatch() {
return (dispatch, getState) => {
const firstState = getState();
dispatch({type : "FIRST_ACTION"});
const secondState = getState();
if(secondState.someField != firstState.someField) {
dispatch({type : "SECOND_ACTION"});
}
}
}
Related
I am using react-redux toolkit with redux thunk to make the api calls. In my app, when I dispatch a thunk for update or delete request and after it succession I want to call the get request as well but only way I was able to figure out is by using a flag with useEffect. Is there any better approach you guys can share?
I also tried this piece in my slice file but it won't work as useDispatch cannot be called inside a javascript function block
.addCase(propertiesThunk.deleteProperty.fulfilled, (state, { payload }) => (
useDispatch(propertiesThunk.getProperties())
))
There's a couple problems here.
The first is that you can never call a React hook outside of a function component, so you definitely can't call useDispatch inside of a reducer.
The second problem is that a reducer can never have side effects. That means it can never dispatch actions. A reducer is only for returning an updated state value.
The simplest answer here would be to have an event handler that does both of these steps back-to-back:
function MyComponent() {
const dispatch = useDispatch()
const handleClick = async () => {
await dispatch(someAsyncThunk())
dispatch(someOtherActionHere())
// or some request, or...
}
}
Another option would be to use the new RTK "listener" side effects middleware, and kick off more logic when some action is dispatched:
startListening({
actionCreator: someAsyncThunk.fulfilled,
effect: (action, listenerApi) => {
// do more work here
}
})
See https://redux-toolkit.js.org/api/createListenerMiddleware for details.
How do I execute custom callback that is passed into an action through react comp, immediately after redux store update.
The idea is say, I trigger an action from react, which will make network request via thunk and dispatches the action with data. This will lead to reducer updating the store. Now, immediately after this I want to redirect to a different page (history.push()) which is a callback.
Using saga middleware it is much easier, but how to implement similar functly using thunk.
You can pass your callback defined in your component the redirect to different page to the thunk and call that after store update is complete. Like this:
function someThunkAction(callback) {
return (dispatch, getState) => {
// Update store logic...
// After update
callback();
};
}
I am little confused about behaviour when I dispatch redux action.
Example:
onPressAdd() {
this.props.addFloor({
name: this.state.floor_name,
});
console.log(this.props.floors);
}
I am calling redux action addFloor what adds floor into array in store, then I console.log this variable and I expecting updated state ([{name:'whatever'}]) but I am getting [] (empty array)
Example 2:
async onPressAdd() {
await this.props.addFloor({
name: this.state.floor_name,
});
console.log(this.props.floors);
}
In this example I am getting perfectly fine updated store: [{name:'whatever'}]
I am reading everywhere that "Redux actions dispatch is sync if there is no thunk or saga (Direct way: dispatch action->reduce->store", but rhis is proof that dispatches are ASYNC.
So where is truth?
Dispatching by itself is 100% synchronous.
This is a tiny implementation of a Redux store:
function createStore(reducer) {
var state;
var listeners = []
function getState() {
return state
}
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
var index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
}
dispatch({})
return { dispatch, subscribe, getState }
}
By the time dispatch() returns, the store has executed your reducer function, and called all the store subscriber callbacks.
It's only when you start adding middleware into the store that the dispatching process can be interrupted, because any middleware can delay, stop, or rewrite any action that was dispatched.
What you're seeing in that example is actually based on how React works. Inside of that click handler, React has not yet re-rendered and updated the props of the component, so this.props.whatever will still be the same before and after the dispatch.
The key thing is to realise that React doesn't update the props of your component until the execution of a called handler is not finished as a macrotask. Hence, your console.log after dispatch doesn't have updated props yet.
Roughly saying, doing your Example 2, you simply split it and get a new microtask with all included after await line, which will be executed after the props are updated.
That would be the same if you do Promise.resolve(this.props.addFloor(...)).then(() => console.log(this.props.floors)) without async/await.
My questions is a conceptual one and based on the issue outlined in this post: React Redux capture updated store state after updating database. I don't think any code is needed to understand or be able to answer it. But if not it is at the link above.
I think I might have missed a small detail about the react/redux state update process following an action that changes the back-end data that a state variable reflects. My question is: When I dispatch a save action, should I then also be dispatching a request to update any state that depends on that underlying data?
So for example, right now the way I'm thinking about it and implementing my code is as follows:
app starts and ParentComponent loads and dispatches GET_DATA on componentDidMount which initializes state variable data which is reflected on ParentComponent in a table
when a link is clicked on ParentComponent, ParentComponent renders ChildComponent which is a react-modal popup that displays elements of data so it can be updated
there is and Save and Close button on ChildComponent; when you click the button, SAVE_DATA is dispatched and the changes to data that are made on ChildComponent get saved to the database
THIS is where my question arises... at this point should I also be calling GET_DATA to dispatch the process of "refreshing" data in my state? Would this be the right way to handle saving data to a database when using redux so that all components that rely on data get updated?
Note: What I'm currently doing is that after step 3, I am simply triggering a refresh function in ParentComponent so that it rerenders and hence reflects data in state. The epiphany I just had is that there is no way for data in state to reflect the new saved data because GET_DATA has not been dispatched after saving and rerendering the component does not trigger GET_DATA.
Are my assumptions correct? Should I be calling GET_DATA somewhere else in my ParentComponent like ComponentWillReceiveProps? The issue I had here is that maybe I'm doing something wrong, but it triggers an endless loop. Somehow though I feel that is the only place where I can address my need to dispatch GET_DATA after the local ParentComponent state is changed by setting refresh (a ParentComponent state variable) to true.
I think it would benefit you to refactor your actions a bit to take advantage of the action/middleware/reducer pattern.
You would have an action GET_TRANSACTIONS, that would take your year param. Your transactionsMiddleware would respond to the GET_TRANSACTIONS action by making your fetch request and would dispatch GET_TRANSACTIONS_SUCCESS with the respond data on success. You transactions reducer would then process the data into your store.
actions
export const getTransactions = year => {
return {
type: "GET_TRANSACTIONS",
year
};
};
export const getTransactionsSuccess = payload => {
return {
type: "GET_TRANSACTIONS_SUCCESS",
payload
};
};
middleware
function getTransactions(year) {
fetch().then(response => dispatch(actions.getTransactionsSuccess(response.data));
}
reducer
const getTransactionsSuccess = (state, action) => {
return Object.assign({}, state, newStuffFromActionPayload);
}
You would also have an action SAVE_TRANSACTIONS, which would be what your button would dispatch, along with the data to save. Your transactionsMiddleware would respond to the action by dispatching the update request. Your API would return the data from the updated record.
This is where you would have the middleware dispatch a follow-up action. It could be your getTransactions action, but it'd be even better to dispatch an action that your reducer would respond to by merging in the new data to your store.
actions
export const updateTransaction = payload => {
return {
type: "UPDATE_TRANSACTION",
payload
};
};
export const updateTransactionSuccess = payload => {
return {
type: "UPDATE_TRANSACTION_SUCCESS",
payload
};
};
middleware
function updateTransaction(transUpdate) {
fetch().then(response => dispatch(actions.updateTransactionSuccess(response.data))
}
reducer
const updateTransactionSuccess = (state, action) => {
find the record in the state, update it with data from action.payload
return Object.assign({}, state, updatedRecord);
}
If everything is set up correctly, it should trigger an update on your parent when it detects the change in the store. You avoid making two API calls for every save as well.
In my project I have action creator that depend on values that are in the state of the application to generate a new value or to decide what action to dispatch. My question is to know which is the right way to do it. I thought of two ways. Access those values within the action creator:
export const changePreviousPage = () => {
return (dispatch, getState) => {
let pagination = getState().appReducers.availability.pagination;
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
dispatch({
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
});
}
};
The other option I thought was to pass the value from the component to the action creator:
In my component
class Pagination extends Component {
...
handlePreviousPage() {
const {pagination} = this.props;
this.props.changePreviousPage(pagination);
}
...
}
In my action creator
export const changePreviousPage = pagination => {
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
return{
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
}
};
What is the best way to address it ?
In my opinion always use/retrieve the state at the closest time to execution, here the action creator (or rather more specifically the thunk you are returning that would then execute).
Remember that dispatch may have any number of middleware running before the actual store.dispatch call. This can include async middleware, so the state may have changed in between calling the dispatch and the store.dispatch call it will ultimately run.
Another one to consider is you may be dispatching multiple things in an action creator which change the state and invalidate what you passed into the action creator at the top. Also a reason why I consider let state = getState() at the top of an action creator a bad idea unless you are very sure nothing is going to change during your processing (as soon as you involve any API calls I would always use getState() again instead of using a stored variable).
Also putting data from state into props (using a redux container and connect helper method) will cause a rerender every time this changes, which could have a performance impact in some cases.
My personal coding preference is also to keep things as simple as possible in mapDispatchToProps (assuming that is where you're passing in your handlers like handlePreviousPage) and avoid any data processing (in your example it's not much, but you can easily see how that may get out of hand if you're preparing data for your action creator).