Redux middleware flow - reactjs

I tried to understand the flow of redux, I know that middlewares wrapped around dispatch (in the applyMiddleware.js) but for the actual middleware such as thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
I am trying to find how the next is being called in the chain. The meaning of next function is to call the next middleware but how is it called? The previous middleware doesnt call the next function as argument but instead it call directly next(action). In short, I want to understand the actual calling chain of middleware and how the currying function do their job. I want to have clear understanding about it to have more control on writing custom middleware.
Once time I saw that someone wrote middleware that the next(action) is being called but after that that the middlware is still handling other stuff. How it would be possible when you pass the control to the next middleware(let say middleware 2) but still executing task in the middleware 1.
Thanks a lot

Related

Why is dispatch returning a promise with no result? (redux middleware)

I came into a React project that uses vanilla Redux with middleware. The way it is setup is as follows:
composeEnhancers(applyMiddleware(...middleware.map(f => f(services))))
Now middlware is an array of, well, middleware containing functions. services is an object containing external services that are injected into the middlware functions (api and so on).
The interesting part is the middleware, here is a sample of it:
...
const throwErrorFlow = ({ api }) => ({ dispatch, getState }) => next => async (action) => {
next(action)
if (action.type === actions.THROW_ERROR) {
try {
dispatch(actions.setLoadingSlot({ state: false, context: action.payload.context }))
const context = getState().ui.context
const payload = { location: action.payload.location, error: action.payload.error?.stack, context }
console.log(payload);
await api.context.throwError(payload)
dispatch(actions.setErrorModalVisibility({ payload, visibility: true }))
} catch (error) {
console.log(error);
}
}
}
const middleware = [
middlwareFunction1,
middlwareFunction2,
...
throwErrorFlow
]
export default middleware
Then I created my own test action that returns a test string. I added a similar middlware function as the rest. When dispatching this test action from the UI and logging its result, all I get is: PromiseĀ {<fulfilled>: undefined}
So I tried zooming in a bit. My action is just the following:
export const customAction = payload => ({
type: CUSTOM_ACTION,
payload: payload,
})
And my bit in the middleware is the following:
const customAsyncActionFlow = () => storeAPI => () => action => {
if (action.type === actions.CUSTOM_ACTION) {
console.log(action);
return 'TEST!'
}
}
const middleware = [
middlwareFunction1,
middlwareFunction2,
...
throwErrorFlow,
customActionFlow
]
export default middleware
And I call it from the UI as:
console.log(dispatch(customAction('Hello World!')));
My action is logged correctly to the console, but then I get PromiseĀ {<fulfilled>: undefined} instead of 'TEST!'. So I removed all other middleware functions and only kept my customActionFlow, and everything worked as I expected. Where is this Promise with no result coming from? Yes all other middleware functions do not return anything, they just modify the state. Does this have to do with this fact? And how do I 'fix' this?
EDIT: okay so I seem to understand what is going on. For each action that requires interaction with the api, a middleware is written for this action which gets applied. In the end there are 20 middleware functions all culminating with the async action for each one. The action that I defined with the test middleware that returns a value gets "lost" in the mix I guess? I am still not sure as to why my return has no effect whatsoever.
Is there a way to make my dispatch action call the my test middleware exclusively while keeping all other middlewares applied?
Oh dear. While this isn't a direct answer to your question...
I've seen that style of "write all Redux logic as custom middleware" tried a few times... and it is a bad idea!
It makes things highly over-complicated, and adding all these extra middleware for individual chunks of functionality adds a lot of overhead because they all have to run checks for every dispatched action.
As a Redux maintainer I would strongly recommend finding better approaches for organizing and defining the app logic. See the Redux Style Guide for our general suggestions:
https://redux.js.org/style-guide/
Now, as for the actual question:
When you call store.dispatch(someAction), the default behavior is that it returns the action object.
When you write a middleware, that can override the return value of store.dispatch(). A common example of this is the redux-thunk middleware, which just does return thunkFunction(dispatch, getState). This is commonly used to let thunks return promises so that the UI knows when some async logic is complete:
https://redux.js.org/tutorials/essentials/part-5-async-logic#checking-thunk-results-in-components
https://redux.js.org/tutorials/fundamentals/part-7-standard-patterns#thunks-and-promises
In this case, the middleware is itself defined as an async function, and every async function in JS automatically returns a promise. So, just having one async middleware in the chain is going to end up returning a promise from store.dispatch(anything). (This would be another reason to not write a bunch of logic directly in a custom middleware like that.)

redux thunk wait data from a dispatch before to do an another dispatch

I'm trying to improve my code by trying to use redux thunk as well as possible but I've searched a lot, no solution corresponds to my research.
Basically, with the twitch API we have to make 2 API calls, one to get the authentication key and another to get the data from this key.
Here is my current code:
dispatch(getOauthKey())
}, [])
useEffect(() => {
if(refresh){
dispatch(getAllStreams(oAuthKey.access_token))
}
}, [oAuthKey.access_token])
Here I had to use a true/false with refresh to know when to dispatch or not and [oAuthkey.access_token] to restart the useEffect when the variable receives data.
It works but it's not optimized at all and I know you can chain dispatches with thunk but I couldn't do it. Here is what I tried to do:
const thunkA = () => (dispatch, getState) => {
dispatch(getOauthKey())
}
const thunkB = (key) =>(dispatch, getState) => {
dispatch(getAllStreams(key))
}
console.log(oAuthKey.access_token)
useEffect(()=> {
dispatch(thunkA()).then(() =>dispatch(thunkB(oAuthKey.access_token))
);
}, [])
And I have this as an error:
TypeError: Cannot read properties of undefined (reading 'then'). I specify that the first call api sends the key into the store so the oAuthKey comes from the latter
Sorry I'm really new to reactjs / redux / thunk and the problem doesn't jump out at me here haha.
thank a lot in advance
There are several options to do that. as a standard redux-thunk solution, you can use promise changing for dispatching the actions in the thunk.
First of all, you need to add this package: redux-promise-middleware with the official documentation.
Given a single action with an async payload, the middleware transforms the action to separate pending action and a separate fulfilled/rejected action, representing the states of the async action.
Now you can chain your actions:
const chainMyActions = () => {
return (dispatch) => {
const response = dispatch(thunkA());
response.then((data) => {
dispatch(thunkB(data.access_token))
})
}
}
Now you can call the sample chainMyActions function in your useEffect.

About Redux-thunk, if a handler dispatches a function to be called, why not call it directly?

As I understand redux-thunk, it can be
<button onClick={fn1}>Click Me</button>
and in fn1, it dispatches not an action object, but dispatches a function fn2, so that when the redux-thunk middleware calls it ( meaning fn2), invoke a fetch or Ajax call, and let the then fulfillment handler or the callback of Ajax dispatch an action object, like
{ type: "DATA_RECEIVED", data: data }
but why doesn't fn1 directly call fn2 to do the task, but let the middleware call it instead?
Redux Thunk is a very thin middleware (like 14 lines thin) that introduces the convention that Redux should know how to deal with asynchronous processes for you.
You said
the thing is you do want fn2 to fire immediately. Such as: user clicks
a button to fetch something, you do want the function that does the
fetch or Ajax to fire immediately. I think with redux-thunk, if you
dispatch a function, it is fired immediately
You're right, you do want it to fire immediately, but do you want to be the one that manually wires up calling the function and passing the dispatch or do you want Redux Thunk to do it for you?
No matter what, Redux is going to need to dispatch an action at some later time. Redux Thunk does the "later" for you.
Consider these differences:
const store = {
dispatch() {
console.log("Dispatch");
}
};
// Action creator that retuns an action
const inc = () => ({
type: "INCREMENT"
});
// Impure action creator that you shouldn't use
const badAsyncInc = () => {
setTimeout(() => {
store.dispatch({
type: "INCREMENT"
});
}, 1000);
};
// Action creator that returns a function that takes dispatch as arg
const aInc = () => dis => setTimeout(() => dis({
type: "INCREMENT"
}), 1000);
/*
* The following will do everything including waiting to dispatch
* and dispatching to the store. That's good, but not good for
* when we want to use a different store.
*/
badAsyncInc()();
/*
* The following is the "manual" way of wiring up the delayed
* dispatch that you have to do without Redux Thunk.
*/
aInc()(store.dispatch);
/*
* With Redux Thunk, this will call the "fn2" immediately but also
* pass the dispatch function to "fn2" for you so that you don't
* have to wire that up yourself.
*/
store.dispatch(aInc);
In this instance, you could just call badAsyncInc and be done since it will call store.dispatch for you. But what if you have multiple stores? What if you want to use a testing harness' store? What if you want to mock the store completely? Your badAsyncInc is now coupled with the store which is bad for action creators.
Alternatively, you can just call aInc yourself and pass it a dispatch function. However, why do that when all your other action creators are probably being written as store.dispatch(actionCreator()). Redux Thunk helps you keep that pattern, even with async actions.
Redux Thunk tries to help you write action creators that are loosely coupled and testable.
in fn1, it dispatches not an action object, but dispatches a function fn2
The motivation behind dispatching a function is that the function includes a dispatch argument (which Redux Thunk provides) for you to use.
Here is an example of how powerful making dispatch available can be:
const action = (url) => async (dispatch) => {
try {
const response = await fetch(url);
dispatch(success(response));
} catch (error) {
dispatch(failure(error));
}
}
Dispatch enables you to dispatch a success or failure action for your reducers to handle.
More details:
https://redux.js.org/advanced/async-actions/#async-action-creators
if a handler dispatches a function to be called, why not call it directly?
If it were called immediately, you couldn't cleanly do the above: dispatching subsequent actions after resolving a fetch or any asynchronous call.

How redux-thunk works?

So I am currently new with redux and stuck in the middleware part. I need to know how these two codes interact with each other.
My action creator:
import jsonPlaceHolder from '../APis/jsonPlaceHolder';
export const fetchPosts= ()=>{
return async (dispatch)=>{
const response = await jsonPlaceHolder.get('/posts');
dispatch({type:'FETCH_POSTS',payload: response});
};
};
redux-thunk code:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Plain redux only works with sync actions. Redux thunk gives you the ability to work with async actions (to dispatch multiple actions from a single action creator, for async actions that is usually the REQUEST/RESPONSE/ERROR action). Middleware is something that stands between you dispatching the action and reducer updating the store. Since redux only works with plain objects, to use a action creator (like fetchPosts) you need something (redux-thunk here). It simply injects the dispatch parameter (and getState that gives you the ability to get the current state if your action creator depends on it).
The next(action) inside middleware is the method that propagates your action object to the next middleware(or if it is the last one to your reducer). Redux-thunk checks if the thing you dispatched is a function(since we said that redux can only work with plain objects), and if it is a function it just injects the above mentioned parameters.
So it is basically:
dispatch(fetchPosts()) -> redux-thunk-middleware -> it is function so
lt's call it with injected dispatch/getState (this will not be propagated to the reducer) ->
dispatch({type:'FETCH_POSTS',payload: response}) ->
redux-thunk-middleware -> not a function, let it through -> reducer ->
update state
Hope this helps.

Redux thunk actions and sharing state

I would like my application to be aware of when async actions are fired and when they complete. The reason for this is that I would like an overall loading state for my applications which sets to true when all async actions have completed. An early implementation of this used a global counter for callbacks.
These actions could be in different files and be listened to by separate reducers, responsible for different branches of state.
I'm using thunk-middleware combined with redux-promise-middleware so one of my async actions might look like this:
export const requestSiteData = () => (dispatch, getState, api) => {
const url = '/endpoint';
return dispatch({
type: 'STATE_BRANCH/REQUEST_SOME_DATA',
payload: api.fetch(url)
}).catch(() => {});
};
This would then dispatch STATE_BRANCH/REQUEST_SOME_DATA_FULFILLED or STATE_BRANCH/REQUEST_SOME_DATA_REJECTED when complete.
Because of the promise middleware I'm finding it hard to intercept the call and count/determine how many ajax requests have been made and how many have completed because the actions are pre-determined and I can't dispatch another action from my reducers.
I can update state there obviously but the async actions may be split across several parts of the state tree so I don't have an overall place to manage the requests.
Does anyone have any ideas how I might go about solving this type of problem. Shout if the description/use case isn't clear.
There is a Redux middleware based around redux-promise that will help you keep track of pending actions:
redux-pending
A thunk is a chain of function that executes one by one so in your case your code for async call should be
export function asyncAction() {
return function(dispatch){
fetchCall().then((data)=>data.json())
.then((data)=>disptach(handleAsyncDataAction(data))
}
and fetchCall() function should be write as.
function fetchCall(){
return fetch('your_url');
}
and your handleAsyncData(data) action will return the result to the reducers so at the end for one fetch call you have to write 3 methods.
1. function you will call from Component
export function asyncAction(){..}
2. function who will written promise to this function
function fetchCall(){...}
3. and last function which will return handle return data and send it to reducer.
function handleAsyncData(data){....}

Resources