I'm working through the Redux fundamentals tutorial. In the section on 'Writing Custom Middleware', we learn that middleware are written as a series of three nested functions like so:
// Outer function:
function exampleMiddleware(storeAPI) {
return function wrapDispatch(next) {
return function handleAction(action) {
// Do anything here: pass the action onwards with next(action),
// or restart the pipeline with storeAPI.dispatch(action)
// Can also use storeAPI.getState() here
return next(action)
}
}
}
exampleMiddleware is explained as follows:
exampleMiddleware: The outer function is actually the "middleware"
itself. It will be called by applyMiddleware, and receives a storeAPI
object containing the store's {dispatch, getState} functions. These
are the same dispatch and getState functions that are actually part of
the store. If you call this dispatch function, it will send the action
to the start of the middleware pipeline. This is only called once.
I didn't understand what was meant by the second last sentence (If you call this dispatch function, it will send the action to the start of the middleware pipeline), so I tried calling store.dispatch(action) inside one of the middlewares provided in src/exampleAddons/middleware.js of the example app to see what happens and got "too much recursion". Here's the demo.
So storeAPI.dispatch() is the composed dispatch function of all the middlewares combined rather than the original store's dispatch, which would explain the recursion. But then what is the use of storeAPI.dispatch()? Am I using it incorrectly?
In applyMiddleware's source:
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// ...1) createStore is called and the resulting store is saved as `store`
const store = createStore(...args)
// ...2) a `dispatch` variable is defined and assigned some function
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// ...3) a middlewareAPI object is defined containing the store's getState method and the `dispatch` function from 2).
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// ...4) the middlewares passed to applyMiddleware are called with the `middlewareAPI` object from 3) and the resulting functions are saved in array `chain`.
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// ...5) the middlewares are composed and the resulting "composed" middleware function is called with `store.dispatch`.
// This returns a composed dispatch function that chains together the `handleAction` functions of all the middlewares passed to applyMiddleware.
// This composed dispatch gets assigned to the `dispatch` variable from 2).
// Since the `storeAPI.dispatch` is referencing this variable, calling `storeAPI.dispatch` now calls the composed middleware function, which causes the infinite loop.
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
The infinite loop seems to be the result of the re-assignment at step 5 in the annotations above. But I'm not sure if my reasoning is correct or if I'm even using storeAPI.dispatch correctly. I would appreciate any guidance the community could provide here as I wasn't able to find any examples of middleware that call storeAPI.dispatch().
Yes, calling storeAPI.dispatch() in a middleware sends the action to the very start of the middleware pipeline. That means that if we have middlewares a->b->c->store, and b calls storeAPI.dispatch({type: "some/action"}), middleware b will see that exact same action object go through almost immediately.
Because of that, a middleware should never call storeAPI.dispatch() unconditionally, because that will cause infinite loops! This is basically the same problem as something like calling setState() unconditionally in a React component useEffect hook. The effect runs after rendering, and setState() queues up another render, so if you always set state every time, you always force a re-render, and that's an infinite loop. Same thing here.
So, any use of storeAPI.dispatch() in a middleware should be wrapped in a conditional check so that it only happens some of the time, not all of the time.
But then what is the use of storeAPI.dispatch()?
Let's examine the naive implementation of thunk middleware. The middleware basically allows dispatch to receive a function (called dispatch function) as its argument (which is normally a plain action object).
const asyncFunctionMiddleware = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState); // (*)
}
return next(action);
}
Async logic will go inside the dispatch function which has been provided with two essential arguments: dispatch and getState - See line (*).
These two arguments are sufficient for developers to interact with the redux store in their async logic. And for the intended use-case, developer's will is to be able to dispatch an action as a normal operation (with all the provided middleware feature). Therefore, a store.dispatch is used here in the implementation.
What happen if we passed next instead?
Depend on the position of the thunk middleware in the middleware list, the next will be the next wrapped dispatch object followed by thunk middleware.
Imagine the order is a->thunk->b->store. Then dispatch using next in thunk will give your no effect of middleware a on the action.
This behavior isn't desired in the thunk use-case, hence using store.dispatch here is appropriate.
Therefore, thunk is a situation that we should make use of store.dispatch in middleware
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
I have defined two functions in my ActionCreator.js file
First:
export const getAudioForVerification = ()=>{
return fetch(baseUrl+'audio',{
// Get Request
}
.then(response=>response.json());}
Second:
export const audioVerificationResult = (audioId,verificationResult) =>(dispatch)=>{
return fetch(baseUrl+'audio',{
// PUT Request
})
.then(response=>response.json());
}
MainFunction:
const mapDispatchToProps = dispatch => ({
getAudioForVerification: ()=>dispatch(getAudioForVerification),
audioVerificationResult: (audioId,verificationResult)=>dispatch(audioVerificationResult(audioId,verificationResult))
});
Q1: If I remove dispatch from my Second function: audioVerificationResult I get an error
Actions must be plain objects. Use custom middleware for async actions.
Why doesn't such an error appear for the first function as well?
Q2: The first function returns a promise (I can use .then in my MainComponent after I call this function) while the second one doesnot. Why?
I have started learning about Promises, Redux and Thunk (Web dev in general) quite recently. If the questions are too broad please direct me to a learning source.
Thank you for your time.
The functions in your ActionCreator.js are not exactly action creators since they are not returning plain object like the redux docs suggests. They are just js functions.
You don't need to dispatch if you are using .then() in your component and reading the results/data. dispatch only helps you with the redux store updates.
Hope this answer helps you to understand granular details about middleware better.
Can anyone tell me what the difference between using chain on a reducer function and doing work in the main index reducer function in redux-auto
I want to save an error,
A) store/chat/send.js
import actions from 'redux-auto'
//...
function rejected(chat, payload, error){
return chat;
} onError.chain = (c, p, error) => actions.logger.save(error)
//...
or
B) store/logger/index.js
import actions from 'redux-auto'
import save from './save'
export default function (errorsLog = [], action)
{
if(action.type == actions.chat.send.rejected){
return save(errorsLog,action.payload)
}
return errorsLog
}
They both work
My questions:
I don't know what would be better. What is the difference?
Why would I use one over the other?
Also, can't I just call the action logger.save(...) inside the
rejected. Why does this chain feature exist?
Thanks for any help :)
A) Using the chain(OnError) will fire the action AFTER the source(rejected) reducer has completed. creating a new call across you store.
B) You are changing the state in the source reducer call
Your qustions:
1,2) Using chaining will make you code more readable as the next function is collocated with the source reducer, but but having it in the index group all action that will happen to that part of the store.
3) Calling an action function directly within a reducer function. Is an anti-pattern. This is dispatching an action in the middle of a dispatched action. The reducer will be operating on inconsistent data.
One of the main Redux point is predictability. We should use as more pure functions as possible. The reducer must not have any side-effects at all.
Recently I've worked on the same feature - error (user action, etc) logging. I think all of this actions are side-effects. They have no profit for user and can't be a part of main business logic.
That's why I use custom middleware to capture all actions I need to log. The action which I need to log I've marked with some meta-prop (ex. {log: 'errorLog'}) and in the middleware I checked every action. If it has a log prop then I make some logger magic.
Finally I've got clearly understandable code where all of logging side-effects incapsulated in middleware.
Looking at the react-redux docs, I don't get the below how about why having an action travel the whole middleware would be useful:
It does a bit of trickery to make sure that if you call
store.dispatch(action) from your middleware instead of next(action),
the action will actually travel the whole middleware chain again,
including the current middleware. This is useful for asynchronous
middleware, as we have seen previously.
What does it mean for an action to travel through the middleware? How does that affect the dispatch? My understanding was that the dispatch changes through each layer of middleware it goes through, and next refers to the previous middlware's dispatch, whereas dispatch refers to the original store.dispatch.
As you can see in the the middleware example, there are multiple middleware items that create a pipe:
rafScheduler -> timeoutScheduler -> thunk -> vanillaPromise -> etc...
An action travels all middleware items before getting to the base reducer or being intercepted by one of the middleware items. Each middleware item can decide to move an action to the next middleware in the chain by using next(). However, sometimes we want the action to travel the chain from the start.
For example, using redux thunk, we dispatch an async action, that will be handled by the thunk middleware. The async action will dispatch another action, when the async call succeeds. This action should start again with the rafScheduler middleware.
If dispatch would have worked like next, it would travel to the vanillaPromise middleware instead. To solve that, dispatch(action), no matter where it was called, always travels the chain from the start.
To create this behavior applyMiddleware() runs over the middleware store => next => action methods, passes the middlewareAPI api to the store param, passes, and overrides the store.dispatch, with a new dispatch that is the combined middleware. This is where the magic happens - the new dispatch is the chain of middleware methods, where each calls the one after it when next is invoked (next = the next middleware method), and the last ones next() is the old store.dispatch that calls the base reducer:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch // this is the original dispatch
var chain = []
/*** this the store param of the middleware ***/
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
/*** store param is applied to old middlewares to create the chain ***/
chain = middlewares.map(middleware => middleware(middlewareAPI))
/*** The chain is composed. For all methods in the chain, but the last, next() is the middleware method after it in the chain, the last next is store.dispatch ***/
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch // the new dispatch is returned instead of the old
}
}
}
I'm learning about react redux middleware from the docs and want to know what he means by "the action will actually travel the whole middleware chain again?" Does that mean the store.dispatch(action) will have the final dispatch that is returned at the end of the middleware, whereas next(action) will only point to the dispatch at a certain point of the middleware function?
It does a bit of trickery to make sure that if you call
store.dispatch(action) from your middleware instead of next(action),
the action will actually travel the whole middleware chain again,
including the current middleware. This is useful for asynchronous
middleware, as we have seen previously.
In redux middleware is a function that patches dispatch function to extend functionality. And next function inside middleware is in fact just a copy of store.dispatch function.
function patchStoreToAddLogging(store) {
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
So when you return next(action) you return a result of dispatch function, with all previous middlewares in the chain applied, to the next middleware.
But when you call store.dispatch function you call a final dispatch method return from middleware chain with all middlewares applied.