Dispatch method in middleware not triggering reducer - reactjs

Using the store method dispatch from the parameter provided by Redux Thunk middleware does not trigger the reducer. While using next() works properly as it triggers the reducer. Why is this happening?
middlerware
export default function createSlimAsyncMiddleware({
dispatch,
getState
}) {
return next => action => {
const {
types,
callAPI,
shouldCallAPI = () => true,
} = action;
if (!actionIsValid(action)) next(action);
if (shouldCallAPI(getState())) {
return Promise.resolve(getState());
}
const [pendingType, successType, errorType] = types;
dispatch({
type: pendingType
});
return callAPI()
.then(response => {
dispatch({ // Does not work, use next()
type: successType,
payload: response,
});
console.log('call resolved with type', successType)
return Promise.resolve(getState());
})
.catch(error => {
dispatch({ // Does not work, use next()
type: errorType,
payload: error,
});
return Promise.reject(error);
});
};
}
store
const store = createStore(
appReducer,
composeWithDevTools(applyMiddleware(
thunk,
createSlimAsyncMiddleware,
routerMiddleware(history)
))
)
Regarding this response https://stackoverflow.com/a/36160623/4428183 the dispatch should also work.

This is stated in the linked response you included, but calling dispatch() will create a new action, which then goes through the entire middleware chain from the beginning. In your case, this includes the middleware you're troubleshooting. From what I can see, the only time you call next() is in the case that an incoming action is deemed invalid. Otherwise, the subsequent API call results in dispatch() being called again whether the call succeeds or fails, and so the action never gets to the reducer because it's constantly being set at the beginning of your middleware chain and never gets to move along via next().
When you say this code doesn't work, what is the specific behavior? Does your app hang? Does it crash? Because this scenario essentially sets up a recursive function with no base case, I'd bet that you're seeing 'maximum call stack exceeded' sorts of errors.
I guess I'd ask why you need to use dispatch() for request results as opposed to sending them along using next(), or why you haven't set this up in a way that sets a conditional that uses the result of the previous call to determine whether the API gets called again.

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: Make http call ONLY IF data not available in store?

I do not want to make an http call unless it is actually required:
This is a workaround I have come up with, I am checking the state before making http call
export const fetchOneApi = (id) => async (dispatch, getState) => {
const docDetails = getState().DocState;
// tricking redux to not send http request unless actually required
if (docDetails.docList[id]) {
return dispatch({
type: FETCH_DOC_SUCCESS,
payload: docDetails.docList[id],
});
}
try {
dispatch({
type: FETCH_DOC_REQUEST,
});
const { data } = await api.get(`/api/${id}`);
dispatch({
type: FETCH_DOC_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: FETCH_DOC_FAIL,
payload: error.error,
});
}
};
Wondering if there is some redux hook or feature that takes care of this OR atleast a better approach.
I've written multiple different custom versions of this functionality for various projects. I toyed with sharing some examples but it's all excessively complicated since I really love to abstract things.
Based on your question, what you are asking for is the createAysncThunk function from redux-toolkit. This function creates an action creator which handles dispatching the pending, fulfilled, and rejected actions at the appropriate times.
There are many ways to customize the behavior of the async thunk. Conditional fetching is described in the docs section "Cancelling Before Execution":
If you need to cancel a thunk before the payload creator is called, you may provide a condition callback as an option after the payload creator. The callback will receive the thunk argument and an object with {getState, extra} as parameters, and use those to decide whether to continue or not. If the execution should be canceled, the condition callback should return a literal false value:
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
},
{
condition: (userId, { getState, extra }) => {
const { users } = getState()
const fetchStatus = users.requests[userId]
if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') {
// Already fetched or in progress, don't need to re-fetch
return false
}
}
}
)
In your example you are short-circuiting by dispatching a success action with redundant data. The standard behavior for the above code is that no action will be dispatched at all if the fetch condition returns false, which is what we want.
We want to store the pending state in redux in order to prevent duplicate fetches. To do that, your reducer needs to respond to the pending action dispatched by the thunk. You can find out which document was requested by looking at the action.meta.arg property.
// example pending action from fetchUserById(5)
{
type: "users/fetchByIdStatus/pending",
meta: {
arg: 5, // the userId argument
requestId: "IjNY1OXk4APoVdaYIF8_I",
requestStatus: "pending"
}
}
That property exists on all three of the dispatched actions and its value is the argument that you provide when you call your action creator function. In the above example it is the userId which is presumably a string or number, but you can use a keyed object if you need to pass multiple arguments.

What is the best approach of writing redux actions that need data from other actions

I have made some research about possible ways to do it, but I can't find one that uses the same architecture like the one in the app I'm working on. For instance, React docs say that we should have a method which makes the HTTP request and then calls actions in different points (when request starts, when response is received, etc). But we have another approach. We use an action which makes the HTTP call and then dispatches the result. To be more precise, my use case is this:
// action to get resource A
getResourceA () {
return dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
};
}
// another action which needs data from resource A
getSomethingElseByIdFromA (aId) {
return async dispatch => {
const result = await axiosClient.get(`someLink/${aId}`);
dispatch({
type: GET_SOMETHING_BY_ID_FROM_A,
payload: result
});
};
}
As stated, the second action needs data from the first one.
Now, I know of two ways of doing this:
return the result from the first action
getResourceA () {
return async dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
return result;
};
}
// and then, when using it, inside a container
async foo () {
const {
// these two props are mapped to the getResourceA and
// getSomethingElseByIdFromA actions
dispatchGetResourceA,
dispatchGetSomethingElseByIdFromA
} = this.props;
const aRes = await dispatchGetResourceA();
// now aRes contains the resource from the server, but it has not
// passed through the redux store yet. It's raw data
dispatchGetSomethingElseByIdFromA(aRes.id);
}
However, the project I'm working on right now wants the data to go through the store first - in case it must be modified - and only after that, it can be used. This brought me to the 2nd way of doing things:
make an "aggregate" service and use the getState method to access the state after the action is completed.
aggregateAction () {
return await (dispatch, getState) => {
await dispatch(getResourceA());
const { aRes } = getState();
dispatch(getSomethingElseByIdFromA(aRes.id));
};
}
And afterward simply call this action in the container.
I am wondering if the second way is all right. I feel it's not nice to have things in the redux store just for the sake of accessing them throughout actions. If that's the case, what would be a better approach for this problem?
I think having/using an Epic from redux-observable would be the best fit for your use case. It would let the actions go throughout your reducers first (unlike the mentioned above approach) before handling them in the SAME logic. Also using a stream of actions will let you manipulate the data throughout its flow and you will not have to store things unnecessary. Reactive programming and the observable pattern itself has some great advantages when it comes to async operations, a better option then redux-thunk, sagas etc imo.
I would take a look at using custom midleware (https://redux.js.org/advanced/middleware). Using middleware can make this kind of thing easier to achieve.
Something like :
import {GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR } from '../actions/actionTypes'
const actionTypes = [GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR ]
export default ({dispatch, getState}) => {
return next => action => {
if (!action.type || !actionTypes.includes(action.type)) {
return next(action)
}
if(action.type === GET_RESOURCE_A){
try{
// here you can getState() to look at current state object
// dispatch multiple actions like GET_RESOURCE_B and/ or
// GET_RESOURCE_A_SUCCESS
// make other api calls etc....
// you don't have to keep stuff in global state you don't
//want to you could have a varaiable here to do it
}
catch (e){
} dispatch({type:GET_RESOURCE_A_ERROR , payload: 'error'})
}
}
}

When does fetch() happen in Redux?

I'm having a problem with controlling the execution of my fetch() functions. Particularly I want to avoid letting the user spam fetch() requests.
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
So my question is, when exactly does a fetch() already get executed?
If it matters, then my code looks roughly like this.
Parent action:
{
return (dispatch) => {
if (mode === 'MY') {
dispatch(myAction(...);
}
dispatch(someOtherAction(...));
}
}
My action:
{
type: 'TYPE',
promise: post(url, payload)
}
My post method:
{
console.log('sending POST');
return fetch(url, {
//fetch info
});
}
My middleware:
{
return next => action => {
const { promise, //other fields } = action;
//Already a promise here.
if (!promise) {
return next(action);
}
return promise.then(
//Processing data
);
};
}
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
To solve this problem, I would do it as a check inside the action. So when you fire the fetch request, dispatch an action that a fetch is currently executing, update the state to say fetching = true. Then in any other action that needs to use fetch, check against that state and return nothing if it's true.
To answer your comment:
When you call an action creator, that is called immediately of course. When you call the dispatch, that will then execute the function (if using redux-thunk). Now, since redux-thunk is a middleware, it executes depending on order that you attached the middleware. So if you put your own middleware in the setup before redux thunk, it will execute before it.
const store = createStore(
combineReducers(reducers),
applyMiddleware(yourMiddleware, ReduxThunk)
);
Otherwise you have the understanding correct. An action fires immediately after calling dispatch, it'll go through the middleware in order, and then pass the action info to your reducers.

how to write async redux actions in a non-fake app

tl;dr: I need an example of an asynchronous redux-thunk action shows how to make an async call (e.g. fetch), and trigger a state update. I also need to see how someone might chain multiple such actions together, like: (1) see if user exists in cloud, then (2) if no, register them, then (3) use the new user record to fetch more data.
All the examples I've found makes the assumption that the redux store can be imported directly into the module that defines the actions. It's my understanding that this is a bad practice: the calling component is responsible for providing access to the store, via this.props.dispatch (which comes from the store being injected via the <Provider>).
Instead, every action in the redux world should return a function that will receive the appropriate dispatch; that function should do the work, and return... something. Obv, it matters what the something is.
Here's the pattern I've tried, based on the documentation, that has proven to be a failure. Nothing in the docs makes it clear why this doesn't work, but it doesn't -- because this action doesn't return a promise.
/**
* pushes a new user into the cloud; once complete, updates the store with the new user row
* #param {hash} user - of .firstName, .lastName
* #return {promise} resolves with user { userId, firstName, lastName, dateCreated }, or rejects with error
*/
Actions.registerUser = function(user) {
return function reduxAction(dispatch) {
return API.createUser(user) // API.createUser just does return fetch(...)
.then(function onUserRegistered(newUser) {
return dispatch({
type: 'ADD_USERS',
users: [newUser]
});
});
};
};
I have a reducer that responds to the ADD_USERS event; it merges the incoming array of one or more users with the array of users already in memory. Reducers are easy to write. That's why I switched to redux: one store, pure functions. But this thunk business is an absolute nightmare.
The error I receive is that .then is undefined on Actions.registerUser -- i.e. that Actions.registerUser doesn't return a promise.
I think the problem is obviously that I'm returning a function -- the reduxAction function -- but that doesn't seem to be negotiable. The only way to shoot data at the store is to use the dispatch method that is provided, and that means I can't return a promise.
Changing the onUserRegistered to simply invoke dispatch and then return the desired value doesn't work either, nor does having it return an actual promise.
PLZ HALP. I really don't get it. I can't believe people put up with all this.
EDIT: To provide some context, here's the kind of action composition I think I'm supposed to be able to perform, and which these thunk actions are frustrating:
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId() // looks for userId in local storage, or generates a new value
.then(Actions.storeUserId) // pushes userId into local storage
.then((userId) => {
return Actions.fetchUsers(userId) // fetches the user, by id, from the cloud
.then((user) => {
// if necessary, pushes the user into the cloud, too
return user || Actions.postUser({ userId: userId, firstName: 'auto-registered', lastName: 'tbd'});
});
})
.then((user) => {
console.log(`boot sequence complete with user `, user);
return dispatch({ type: 'ADD_OWNER', user });
});
};
};
I would expect that Actions.storeUserId and Actions.fetchUsers would, in addition to returning promises that resolve with values of my choosing, dispatch data to the store as a side-effect. I think the dispatch is occurring, but the chain breaks because none of these actions return promises - they return plain functions.
Not only does this seem much worse than Flux, it seems incomprehensible. I can't believe that all this madness was necessary just to consolidate app state into a single reducing store.
And yes -- I have tried the new version of flux, with its ReducerStore, but it has some inappropriate dependencies on CSS libraries that are incompatible with react-native. The project maintainers have said they don't intend to resolve the issue. I guess their state container is dependent on CSS functionality.
EDIT: my store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import Reducers from './reducers';
const createStoreWithMiddleWare = applyMiddleware(thunk)(createStore);
export const initialState = {
users: [] // will hold array of user objects
};
const store = createStoreWithMiddleWare(Reducers);
export default store;
EDIT: Here's the calling code. This is the root-level react-native component.
// index.ios.js
import Store from './store';
class myApp extends Component {
componentDidMount() {
Store.dispatch(Actions.bootSetup())
.then(() => {
console.log('*** boot complete ***');
});
}
render() {
return (
<Provider store={Store}>
<ApplicationRoutes />
</Provider>
);
}
}
My assumption is that Store.dispatch expects a function, and provides it with a reference to the store's dispatch method.
I can see one mistake right off the bat
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId()
You aren't chaining thunk actions correctly. If your actions returns a function, you need to pass dispatch to that action.
Take a look at this action creator(this is a fully functional real-world app, feel free to poke around), look at the 9th line, where loginUser is called.
export function changePassword(credentials) {
return (dispatch, getState) => {
dispatch(changePasswordStart(credentials))
return Firebase.changePassword(credentials)
.then(() => {
return logout()
})
.then(() => {
return loginUser(credentials.email, credentials.newPassword)(dispatch)
})
.then(() => {
dispatch(changePasswordSuccess(credentials))
toast.success('Password successfully changed')
}).catch(error => {
dispatch(changePasswordError(error.code))
toast.error('An error occured changing your password: ' + error.code)
})
}
}
Because loginUser is also a thunk action, it needs to have dispatch passed to the result of calling it. It makes sense if you think about it: the thunk doesn't do anything, it just creates a function. You need to call the function it returns to get it to do the action. Since the function it returns takes dispatch as an argument, you need to pass that in as well.
Once that's done, returning a promise from a thunk action will work. In fact, the example I gave above does exactly that. loginUser returns a promise, as does changePassword. Both are thenables.
Your code probably needs to look like this (though I am not sure, I don't have the actions being called)
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId()(dispatch) // pass dispatch to the thunk
.then(() => Actions.storeUserId(dispatch)) // pass dispatch to the thunk
.then((userId) => {
return Actions.fetchUsers(userId)(dispatch) // pass dispatch to the thunk
.then((user) => {
// pass dispatch to the thunk
return user || Actions.postUser({ userId: userId, firstName: 'auto-registered', lastName: 'tbd'})(dispatch);
});
})
.then((user) => {
console.log(`boot sequence complete with user `, user);
return dispatch({ type: 'ADD_OWNER', user });
});
};
};

Resources