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){....}
Related
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.)
use effect hook is used to perform the side effects like network requests in react.
redux-thunk middleware is also used to perform the side effects like network requests in react.
I'm pretty confused, is there any difference in their real application, or is it just a matter of choice.
The purpose of thunk is not to perform side effects by definition.
In Redux world, the actions must be plain objects with a required key type. An example:
const increaseAction = { type: "INCREASE" };
If you want to create a function that returns an action, this function should also return an action object but nothing else. Otherwise you cannot dispatch the action creator function itself.
// Create an action creator
const increase = () => {
return { type: "INCREASE" };
}
// Now you can dispatch the result of this function
dispatch(increase());
However, when dealing with asynchronous network requests, you probably want to dispatch multiple actions that updates your state accordingly based on the current state of your network request.
// When starting network request
dispatch({ type: "FETCH_START" })
// When network request is successful
dispatch({ type: "FETCH_SUCCESS" })
// When network request fails
dispatch({ type: "FETCH_ERROR" })
That's why action creator functions that deals with network requests or asynchronous operations return another function that takes dispatch as its parameter. This return function is handled by thunk middleware. Now we can use the dispatch function from the parameter to dispatch our actions.
const fetchData = () => async (dispatch) => {
dispatch({ type: "FETCH_START" });
try {
const data = await fetch("http://somedata.com/something").then(res => res.json());
dispatch({ type: "FETCH_SUCCESS", payload: data });
} catch {
dispatch({ type: "FETCH_ERROR" });
}
}
If you realized, we did not return anything inside fetchData. Instead, we used the dispatch parameter from the function that is returned by fetchData. When you dispatch(fetchData()), thunk transforms your action creator functions into plain objects; wait for the network requests to be resolved or rejected, then dispatch the appropriate action based on the result of your network request.
Now where does useEffect fall into this equation?
useEffect is the React hook that mimics the React lifecycle methods from class components. If you want to make a network request, or any asynchronous operation, you can do it inside useEffect. Following the Redux example above, you would call dispatch(fetchData()) inside useEffect.
Redux thunk is if you are using redux and are doing something asynchronously. E.g such as writing to a database.
if you are just using functional components in React and you want to update the ui then you would use useEffect to check for the change. If you are using class based components then there is a built in method componentDidMount. Built in as in you don't have to import it in along with React. Which you need to do for useEffect.
Here is the page for hooks, that talks about how it is used.
https://reactjs.org/docs/hooks-effect.html
Here is the page for thunks
https://redux.js.org/usage/writing-logic-thunks
How to dispatch action in other reducer.
For example i create reducer LogsReducer with switch case ADD_LOG and REMOVE_LOG. Code:
const addLog = (text) => ({type: 'ADD_LOG', text});
const removeLog = (id) => ({type: 'REMOVE_LOG', id});
const logsReducer = (state = {}, action) => {
switch (action.type) {
case 'ADD_LOG':
// todo
case 'REMOVE_LOG':
// todo
default:
return state;
}
};
And i add logsReducer to combineReducers.
To dispatch action i call this inside mapDispatchToProps.
dispatch(addLog('sample log'));
dispatch(removeLog(2);
And here question. How to allow to dispatch message from outside of logs reducer. For example from fetch response in other reducer eg. contactReducer?
How to allow to dispatch message from outside of logs reducer. For example from fetch response in other reducer eg. contactReducer?
Usually presence of asynchronous API calls or action dispatching queue meant that presentation/domain logic is consists of process, so process-manager is needed for resolve that task.
General solution is redux-saga, which combines middleware and live process manager. With using saga, components just notify about logical happened action, and then saga performs API calls, produce new actions and even do Optimistic updates.
Also, with redux+saga approach your web-application automatically become full-stack Event-Sourced application: by writing front-end code you will get also isomorphic back-end code (If use some tool like that https://github.com/reimagined/resolve )
See practices with using saga Infinite loop with redux-saga and how to setstate after saga async request
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
I am trying to chain multiple actions together in the following fashion:
A. post user data to database
B. use posted data to query Elasticsearch for results
(I do A and B in parallel)
B1. with results from ES, query original database for results from two tables
B2. navigate to new page and update UI
I am using thunks right now to reason about my code, but I also found this async pattern to be extremely verbose:
export function fetchRecipes(request) {
return function(dispatch) {
dispatch(requestRecipes(request))
return fetch(url)
.then(response => response.json())
.then(json => dispatch(receiveRecipes(request, json))
)
}
}
this, along with "requestRecipes" and "receiveRecipes" as other action creators seems like quite a bit just to make one async call. (a request, a receive, and a fetch function)
summary: when you're chaining 2-3 async actions whose outputs depend on each other (I need to promisify when possible), is there a more efficient means of doing so without writing 3 functions for each async call?
I figure there had to be a way. I'm pattern matching off of the Redux docs and soon became very overwhelmed with the functions I was creating
thanks a lot for the feedback!
You can use redux-saga instead of redux-thunk to achieve this more easily. redux-saga lets you describe your work using generators and is easier to reason about.
The first step is to describe how you pass your data to redux without worrying about services or async stuff.
Actions
// actions.js
function createRequestTypes(base) {
return {
REQUEST: base + "_REQUEST",
SUCCESS: base + "_SUCCESS",
FAILURE: base + "_FAILURE",
}
}
// Create lifecycle types on `RECIPES`
export const RECIPES = createRequestTypes("RECIPES")
// Create related actions
export const recipes = {
// Notify the intent to fetch recipes
request: request => ({type: RECIPES.REQUEST, request})
// Send the response
success: response => ({type: RECIPES.SUCCESS, response})
// Send the error
error: error => ({type: RECIPES.FAILURE, error})
}
Reducer
// reducer.js
import * as actions from "./actions"
// This reducer handles all recipes
export default (state = [], action) => {
switch (action.type) {
case actions.RECIPES.SUCCESS:
// Replace current state
return [...action.response]
case actions.RECIPES.FAILURE:
// Clear state on error
return []
default:
return state
}
}
Services
We also need the recipes API. When using redux-saga the simplest way to declare a service is to creating a (pure) function which reads the request as argument and returns a Promise.
// api.js
const url = "https://YOUR_ENPOINT";
export function fetchRecipes(request) {
return fetch(url).then(response => response.json())
}
Now we need to wire actions and services. This is where redux-saga come in play.
// saga.js
import {call, fork, put, take} from "redux-saga/effects"
import * as actions from "./actions"
import * as api from "./api"
function* watchFetchRecipes() {
while (true) {
// Wait for `RECIPES.REQUEST` actions and extract the `request` payload
const {request} = yield take(actions.RECIPES.REQUEST)
try {
// Fetch the recipes
const recipes = yield call(api.fetchRecipes(request))
// Send a new action to notify the UI
yield put(actions.fetchRecipes.success(recipes))
} catch (e) {
// Notify the UI that something went wrong
yield put(actions.fetchRecipes.error(e))
}
}
}
function* rootSaga() {
yield [
fork(watchFetchRecipes)
]
}
And that's it! Whenever a component will send a RECIPES.REQUEST action, the saga will hook up and handle the async workflow.
dispatch(recipes.request(req))
What's awesome with redux-saga is that you can easily chain async effects and dispatch actions during the workflow.
Based on your description, the only time you actually update your UI is right at the end of all these asynchronous operations (B1).
If you don't use the results from the preceding async calls to change your application state / update your UI, what is the benefit of having these fine-grained actions?
Of course there are things like "loading / request started" and "finished loading / request stopped", but it seems to me, that in your case, you could just do the chained async calls outside of redux (in some kind of API-layer) and only use one action.
This action dispatches a "REQUEST_STARTED", then calls the API-layer, which does the DB-calls and elasticsearch request etc., and then dispatches either "REQUEST_SUCCESS" or "REQUEST_FAILURE", based on the result of the promise, which will give you the data you need to update your UI.
This way, the state in redux only concerns itself with ONE side-effect, instead of the implementation details of your chained calls. Also, your action gets a lot simpler, because it just handles the results of one async call.