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
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.)
Good evening,
I am making weather app with react and redux. I have my reducer with a object of my data info with empty string etc:
const initState = {
city: '',
temp: '',
wind: ''
}
And for example in React I am gonna fetch all the data and what should i do now?
I should have dispatch action (FILL DATA - for example) and then i should make a [useState] inside my component for temporary place for my data. Then fill the data in my temporary place and then useDispatch to update redux store?
Actually, there are a few ways to do this, such as:
Use a redux middleware like redux-saga or redux-thunk, which I recommend.
In the request action, make the asynchronous call and then dispatch the success action to update the state data.
Call the API from the component and call the state redux store update action to update the global state. NOT RECOMMENDED.
Apparently, you're trying to use the third way, but it's not recommended because it beats the purpose of abstract redux and making the data scattered all around. A bad practice.
A middleware example would be very long, so I'll try to explain the second way briefly.
In your request action, do something like this:
...
axios.get(url).then(res => {
dispatch({ type: 'REQUEST_SUCCESS', data: res.data });
});
In your reducer:
...
switch (action.type) {
case: 'REQUEST_SUCCESS';
return { ...state, ...action.data };
}
There is a library called redux-thunk that will help you with this. It allows actions to be asynchronous, so you can actually dispatch the action, fetch all the data INSIDE the action and then dispatch an action to fill your state. After configuring it, your action would look something like this:
const getWeatherData = () => { // Declare it like any other action
return async (dispatch) => { //Then return a promise
const response = await getWeatherAPI(); // fetch from the backend
return dispatch({ // And then dispatch another action to populate your reducer
type: "FILL_DATA",
payload: response
})
}
}
This action would be dispatched from your code just like any other action, but considering it returns a promise you can actually await for it to finish and handle that any way you want.
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.
I am trying to load data when my component loads using componentDidMount. However calling the Redux action, making the call with axios seems to freeze the UI. When I have a form with 12 inputs and one makes an API call I would assume I can type in the other inputs and not have them freeze up on me.
I've tried reading some other posts on the subject but they are all a little different and everything I have tried doesn't seem to resolve the issue.
I am working on linux using React 16.8 (when using RN I use 55.4)
I have tried making my componentDidMount async as well as the redux-thunk action. It didn't seem to help anything, so I must be doing something wrong.
I tried doing the following with no success. Just using short form for what I tried. Actual code listed below.
async componentDidMount() {
await getTasks().then();
}
And I tried this
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
Current Code:
Component.js
componentDidMount() {
const { userIntegrationSettings, getTasks } = this.props;
// Sync our list of external API tasks
if (!isEmpty(userIntegrationSettings)) {
getTasks(userIntegrationSettings.token)
// After we fetch our data from the API create a mapping we can use
.then((tasks) => {
Object.entries(tasks).forEach(([key, value]) => {
Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
});
});
}
}
Action.js
export const getTasks = () => ((dispatch, getState) => {
const state = getState();
const { token } = state.integrations;
const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
const AJAX_CONFIG = getAjaxHeaders(token);
dispatch(setIsFetchingTasks(true));
return axios.get(`${URL}`, AJAX_CONFIG)
.then((response) => {
if (!isEmpty(response.data)) {
response.data.forEach((task) => {
dispatch(addTask(task));
});
return response.data;
} else {
dispatch(setIsFetchingTasks(false));
}
})
.catch((error) => {
dispatch(setIsFetchingTasks(false));
errorConsoleDump(error);
errorHandler(error);
});
});
reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case ADD_TASK:
case UPDATE_TASK:
return update(state, {
byTaskId: { $merge: action.task },
isFetching: { $set: false }
});
default:
return state;
}
};
So in my answer what are you going to learn?
General data loading with Redux
Setting up a component lifecycle method such as componentDidMount()
Calling an action creator from componentDidMount()
Action creators run code to make an API request
API responding with data
Action creator returns an action with the fetched data on the payload property
Okay, so we know there are two ways to initialize state in a Reactjs application, we can either invoke a constructor(props) function or we can invoke component lifecycle methods. In this case, we are doing component lifecycle methods in what we can assume is a class-based function.
So instead of this:
async componentDidMount() {
await getTasks().then();
}
try this:
componentDidMount() {
this.props.fetchTasks();
}
So the action creators (fetchTasks()) state value becomes the components this.props.fetchTasks(). So we do call action creators from componentDidMount(), but not typically the way you were doing it.
The asynchronous operation is taking place inside of your action creator, not your componentDidMount() lifecycle method. The purpose of your componentDidMount() lifecycle method is to kick that action creator into action upon booting up the application.
So typically, components are generally responsible for fetching data via calling the action creator, but it's the action creator that makes the API request, so there is where you are having an asynchronous JavaScript operation taking place and it's there where you are going to be implementing ES7 async/await syntax.
So in other words it's not the component lifecycle method initiating the data fetching process, that is up to the action creator. The component lifecycle method is just calling the action creator that is initiating the data fetching process a.k.a. the asynchronous request.
To be clear, you are able to call this.props.fetchTasks() from your componentDidMount() lifecycle method after you have imported the action creator to your component like and you have imported the connect function like so:
import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";
You never provided the name of the component you are doing all this in, but at the bottom of that file you would need to do export default connect(null, { fetchTasks })(ComponentName)
I left the first argument as null because you have to pass mapStateToProps, but since I don't know if you have any, you can just pass null for now.
Instead of this:
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
try this:
export const fetchTasks = () => async dispatch => {
const response = await axios.get(`${URL}`, AJAX_CONFIG);
dispatch({ type: "FETCH_TASKS", payload: response.data });
};
There is no need to define getState in your action creator if you are not going to be making use of it. You were also missing the dispatch() method which you need when developing asynchronous action creators. The dispatch() method is going to dispatch that action and send it off to all the different reducers inside your app.
This is also where middleware such as Redux-Thunk comes into play since action creators are unable to process asynchronous requests out of the box.
You did not show how you wired up your redux-thunk, but it typically goes in your your root index.js file and it looks like this:
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
Remember that connect function I said you needed to implement? That came into being as a result of implementing or you should have implemented the Provider tag. With the Provider tag, your components can all have access to the Redux store, but in order to hook up the data to your components you will need to import the connect function.
The connect function is what reaches back up to the Provider and tells it that it wants to get access to that data inside whatever component you have that lifecycle method in.
Redux-Thunk is most definitely what you needed to implement if you have corrected everything as I have suggested above.
Why is Redux-Thunk necessary?
It does not have anything intrinsically built into it, it's just an all-purpose middleware. One thing that it does is allow us to handle action creators which is what you need it to be doing for you.
Typically an action creator returns an action object, but with redux-thunk, the action creator can return an action object or a function.
If you return an action object it must still have a type property as you saw in my code example above and it can optionally have a payload property as well.
Redux-Thunk allows you to return either an action or function within your action creator.
But why is this important? Who cares if it returns an action object or a function? What does it matter?
That's getting back to the topic of Asynchronous JavaScript and how middlewares in Redux solves the fact that Redux is unable to process asynchronous JavaScript out of the box.
So a synchronous action creator instantly returns an action with data ready to go. However, when we are working with asynchronous action creators such as in this case, it takes some amount of time for it to get its data ready to go.
So any action creator that makes an network request qualifies as an asynchronous action creator.
Network requests with JavaScript are asynchronous in nature.
So Redux-Thunk, being a middleware which is a JavaScript function that is going to be called with every single action that you dispatch. The middleware can stop the action from proceeding to your reducers, modify the action and so on.
You setup dispatch(setIsFetchingTasks(true)) but when axios returns you never set it to false. Did you miss to add dispatch(setIsFetchingTasks(false)) before return response.data;?
This could be the reason if your UI waits for the fetchingTasks to finish
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.