I am relatively newer to redux. I have gone through a lot of article still i am not getting a clear picture whats the real benefit of using redux-thunk.
All I understood is it allows you to return a function instead of object from action-creators? But what's the benefit? I have created few small react projects without using redux-thunk.
Let's consider the below snippets. Both behaves the same.
It would be great help if someone can explain me or point me to the correct resources to get a better understanding.
With redux-thunk
export function fetchContacts(){
return function(dispatch){
axios
.get('/contacts')
.then( contacts => dispatch({ type: 'FETCH_CONTACTS', payload: contacts}))
}
}
Without redux-thunk
const client = axios.create({
baseURL: "http://localhost:3000",
headers: {
"Content-Type": "application/json"
}
})
const url = '/contacts';
export function fetchContacts(){
return {
type: 'FETCH_CONTACTS',
payload: client.get(url)
}
}
The purpose of Redux itself, is to hold our application state. One of the great features of Redux is that we can change our state in a well-defined pattern and it's a pattern that we repeat over and over in our applications.
We call an Action Creator, this produces an Action. The Action flows into our Middleware, then our Reducers which then flows into our application State then into React. Then we sit around and wait for the user to initiate some change inside of our application that repeats the process all over again.
This process works fine with any kind of synchronous change. By synchronous I mean that we call an Action Creator which immediately flows into an Action, Middleware and our Reducers.
The vast majority of web applications we build, however, need to fetch data from asynchronous channels. In other words, its more common to call an Action Creator that is fetching data from an API or some asynchronous action and only when that request resolves are we actually ready to create an Action.
Vanilla Redux is not setup to handle this out of the box.
So, how do we handle these asynchronous Action Creators?
This is where Redux-Thunk comes into play. The purpose of Redux-Thunk is to give us direct control over the Dispatch method. The Dispatch method is part of the ReduxStore that contains our application state.
The Dispatch method handles:
Middleware
Reducers
State
When we normally call an Action Creator and it returns an Action, the Action ends up being returned into this Dispatch method. You have been using the Dispatch method behind-the-scenes in vanilla Redux.
So in practice, say you have a file in actions/index.js:
import axios from 'axios';
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
}
Redux expects us to return an action, but we do not yet have any data. I have to wait for my request to resolve before I have any data to send across to my Dispatch method.
So, lets use Redux-Thunk where all the existing rules for action creators kind of go out the window. Redux expects for us to return an Action which is a plain JavaScript object.
Redux-Thunk enables one other return type, which is a plain JavaScript function.
import axios from 'axios';
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
return () => {
};
}
The first argument is going to be the dispatch method:
import axios from 'axios';
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
return (dispatch) => {
};
}
If we pass an Action into dispatch, it's going to be sent off to all our different reducers.
export function fetchUsers() {
const request = axios.get('http://somejsondata.com/users');
return (dispatch) => {
request.then(({data}) => {
dispatch({type: 'FETCH_PROFILES', payload: data})
});
};
}
This is saying, we are going to wait for request to resolve with some amount of data and only when it has will I dispatch an action. In this case, it is going to have type FETCH_PROFILES and payload of data.
redux-thunk allows you to execute asynchronous operations.
In your first example you are sending the actual data returned by your API endpoint to your reducer. And the action will only be sent after the server has returned the data.
In your second example, you are sending a promise to your reducer which doesn't work as you would have to resolve your promise inside the reducer, which breaks the principle upon which reducers should be pure functions.
redux-thunk allows you to delay your actions in order to make async calls before dispatching. lets say you are retrieving user settings. the common use-case is to dispatch a REQUEST_FOR_USER_SETTINGS_IN_PROGRESS action so you can show a loader in your app, then make the http request for the data and when you get a response, dispatch a SUCCESS or ERROR action to update UI. it would look something like this:
const requestToGetCoins = await() => {
return (dispatch) => {
dispatch(requestToGetUserSettingsInProgress());
try{
const users = async httpService.getUserSettings();
dispatch(requestToGetUserSettingsSuccess(users));
}
catch(e){
dispatch(requestToGetUserSettingsError(e));
}
};
};
just wanted to emphasise that there is a better way to handle async actions in redux than redux-thunk, using an adhoc middleware which handles async actions and reduces much of the boilerplate. I suggest you take a look at this:
https://medium.com/#sht_5/minimal-code-for-redux-async-actions-c47ea85f2141
Related
I have an app that fetches some user info on render. So when the app first boots up it fetches the data with the use of getUserInformation() function. User doesn't need to manually log in, the app is inside the company's internal network.
export function getUserInformation() {
return function (dispatch) {
getUser()
.then((data) => {
dispatch(
{type: GET_USER_SUCCESS, response: data}
)
})
.catch((error) => {
dispatch(
{type: GET_USER_FAILURE, response: error}
)
})
}
}
Now I want to fetch the version of the app to be available in the whole app. But the API call can only be fired once the user is logged in (so getUser() was called successfully). Should I just add the
.then(getVersion())
in the getUserInformation() action?
It doesn't seem clean but I have no idea how can I approach it differently.
Action creator is a proper place to dispatch actions in sequence. The documentation covers this:
Using an async middleware like Redux Thunk certainly enables scenarios such as dispatching multiple distinct but related actions in a row, dispatching actions to represent progression of an AJAX request, dispatching actions conditionally based on state, or even dispatching an action and checking the updated state immediately afterwards.
In case user information and version actions need to be tested separately (they should be located in different modules) or be used separately, action creators can be combined. This requires to return promises to chain them. This also shows the limitation of redux-thunk:
function getUserInformation() {
return async (dispatch) => {
try {
dispatch(
{type: GET_USER_SUCCESS, response: await getUser()}
)
} catch (error) {
dispatch(
{type: GET_USER_FAILURE, response: error}
)
}
};
}
...
function getVersion() {
return async (dispatch) => {...};
}
...
function getInitialData() {
return async (dispatch, getState) => {
await getUserInformation()(dispatch);
// we need to use getState to check if there was an error
// because getUserInformation returns a fulfilled promise any way
await getVersion()(dispatch);
};
}
It would make sense be to re-throw an error from getUserInformation, but it would be bad in case it's used separately from getInitialData because this would result in unhandled rejection. The alternative is even worse, to check if there was an error with getState().
This scenario requires a more sophisticated middleware than redux-thunk which is dead simple - possibly a custom middleware that is based on it and is capable of handling rejections.
I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!
Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.
This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.
Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.
Edit: My views on the Vuex cycle of uni-directional data flow:
If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.
The promise from action is only to let the component know that action is complete.
The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.
The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.
Just for an information on a closed topic:
you don’t have to create a promise, axios returns one itself:
Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4
Example:
export const loginForm = ({ commit }, data) => {
return axios
.post('http://localhost:8000/api/login', data)
.then((response) => {
commit('logUserIn', response.data);
})
.catch((error) => {
commit('unAuthorisedUser', { error:error.response.data });
})
}
Another example:
addEmployee({ commit, state }) {
return insertEmployee(state.employee)
.then(result => {
commit('setEmployee', result.data);
return result.data; // resolve
})
.catch(err => {
throw err.response.data; // reject
})
}
Another example with async-await
async getUser({ commit }) {
try {
const currentUser = await axios.get('/user/current')
commit('setUser', currentUser)
return currentUser
} catch (err) {
commit('setUser', null)
throw 'Unable to fetch current user'
}
},
Actions
ADD_PRODUCT : (context,product) => {
return Axios.post(uri, product).then((response) => {
if (response.status === 'success') {
context.commit('SET_PRODUCT',response.data.data)
}
return response.data
});
});
Component
this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
if (res.status === 'success') {
// write your success actions here....
} else {
// write your error actions here...
}
})
TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.
For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.
But, there are EDGE CASES where returning a promise from your actions might be "necessary".
Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently.
In that case, one would need to pass the caller component as a parameter to set different flags in the store.
Dumb example
Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous.
If the promise fails, the page should only display an error in the component the user was trying to change the username from.
Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.
actions.js
const axios = require('axios');
const types = require('./types');
export const actions = {
GET_CONTENT({commit}){
axios.get(`${URL}`)
.then(doc =>{
const content = doc.data;
commit(types.SET_CONTENT , content);
setTimeout(() =>{
commit(types.IS_LOADING , false);
} , 1000);
}).catch(err =>{
console.log(err);
});
},
}
home.vue
<script>
import {value , onCreated} from "vue-function-api";
import {useState, useStore} from "#u3u/vue-hooks";
export default {
name: 'home',
setup(){
const store = useStore();
const state = {
...useState(["content" , "isLoading"])
};
onCreated(() =>{
store.value.dispatch("GET_CONTENT" );
});
return{
...state,
}
}
};
</script>
I think I am missing a fundamental piece of understanding here. I am aware that in order for an action to be created and thus set off the redux chain of events, the action creator must be invoked using dispatch.
However, when we have a redux-thunk which returns a function which will call dispatch on our action creator, why must the redux-thunk also be called with dispatch?
For example take the following redux-thunk:
function savePerson(person: Person) {
return async (dispatch: any) => {
delete person.cars;
let newPerson = await axios.post('/api/people/addPeron', person);
dispatch(addPersonSuccess(person));
}
}
Calling this savePerson function without dispatch does not set off the redux flow, and I don't understand why considering the function it returns calls our action creator with dispatch. Can anybody clarify what I seem to be missing here?
All redux middleware follows the same general layout:
const middleware => store => next => action => next(action);
Why must the redux-thunk also be called with dispatch?
Like you correctly pointed out in your first paragraph, for an action/thunk to be evaluated by the redux middleware chain, it must be dispatched by the calling code.
I think the misunderstanding comes in here:
"...when we have a redux-thunk which returns a function which will
call dispatch on our action creator...".
While it is correct that the returned function dispatches an action, it is only half the story. Technically, you are dispatching twice: first savePerson and later addPersonSuccess. The former being a thunk and the latter, most likely, being a plain action.
Now, let's consider the current redux-thunk source code:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
...
export default thunk;
Once you dispatch savePerson, the middleware recognizes your action as a function. It then injects dispatch as the first argument to later allow dispatching of other actions. Up until now, dispatch has not been invoked on your addPersonSuccess action. Only after your asynchronous call to add a person will the dispatch be called on addPersonSuccess.
I like to think of this as passing the redux context along in the thunk (dispatch, getState, etc).
References
http://redux.js.org/docs/advanced/Middleware.html
https://github.com/gaearon/redux-thunk
I am following redux-saga documentation on helpers, and so far it seems pretty straight forward, however I stumbled upon an issue when it comes to performing an api call (as you will see link to the docs points to such example)
There is a part Api.fetchUser that is not explained, thus I don't quiet understand if that is something we need to handle with libraries like axios or superagent? or is that something else. And are saga effects like call, put etc.. equivalents of get, post? if so, why are they named that way? Essentially I am trying to figure out a correct way to perform a simple post call to my api at url example.com/sessions and pass it data like { email: 'email', password: 'password' }
Api.fetchUser is a function, where should be performed api ajax call and it should return promise.
In your case, this promise should resolve user data variable.
For example:
// services/api.js
export function fetchUser(userId) {
// `axios` function returns promise, you can use any ajax lib, which can
// return promise, or wrap in promise ajax call
return axios.get('/api/user/' + userId);
};
Then is sagas:
function* fetchUserSaga(action) {
// `call` function accepts rest arguments, which will be passed to `api.fetchUser` function.
// Instructing middleware to call promise, it resolved value will be assigned to `userData` variable
const userData = yield call(api.fetchUser, action.userId);
// Instructing middleware to dispatch corresponding action.
yield put({
type: 'FETCH_USER_SUCCESS',
userData
});
}
call, put are effects creators functions. They not have something familiar with GET or POST requests.
call function is used to create effect description, which instructs middleware to call the promise.
put function creates effect, in which instructs middleware to dispatch an action to the store.
Things like call, put, take, race are effects creator functions. The Api.fetchUser is a placeholder for your own function that handles API requests.
Here’s a full example of a loginSaga:
export function* loginUserSaga() {
while (true) {
const watcher = yield race({
loginUser: take(USER_LOGIN),
stop: take(LOCATION_CHANGE),
});
if (watcher.stop) break;
const {loginUser} = watcher || {};
const {username, password} = loginUser || {};
const data = {username, password};
const login = yield call(SessionService.login, data);
if (login.err === undefined || login.err === null && login.response) {
yield put(loginSuccess(login.response));
} else {
yield put(loginError({message: 'Invalid credentials. Please try again.'}));
}
}
}
In this snippet, the SessionService is a class that implements a login method which handles the HTTP request to the API. The redux-saga call will call this method and apply the data parameter to it. In the snippet above, we can then evaluate the result of the call and dispatch loginSuccess or loginError actions accordingly using put.
A side note: The snippet above is a loginSaga that continuously listens for the USER_LOGIN event, but breaks when a LOCATION_CHANGE happens. This is thanks to the race effect creator.
I want to model the following async logic using redux:
User action triggers a chain of async API calls.
Any API call might return 401 status (login timed out)
If API responds with 401, display re-login popup
On successful re-login, reissue API call and continue
I am not sure where to put this logic. Actions don't know about other actions, they only have access to dispatch, so they can't stop and wait for them to complete. Reducers don't have access to dispatch, so I can't put it there… so where does it live? Custom middleware? store.listen? In a smart component?
I'm currently using redux-promise-middleware & redux-thunk. How would one best organise this type of flow – without requiring buy-in into something like redux-saga or redux-rx, etc?
Also not sure best way to transparently interrupt the API call to perform those other actions i.e. API call shouldn't trigger its completed or failed actions until after the optional login process completes.
It sounds to me like you'd want an action creator that generates a Thunk, and keep all that logic in the Thunk. There's really no other good way to preserve the association between your suite of API calls, and ensure that all the others are cancelled if one fails.
In that Thunk, you'd fire your API calls, and collect their promises:
const call1 = promiseGenerator1();
const call2 = promiseGenerator2();
const call3 = promiseGenerator3();
const allCallPromises = [call1, call2, call3];
Use an all() promise handler to monitor them:
const watcher = Promise.all(allCallPromises).then(allSuccess, anyFail);
Your fail handler will:
cancel the rest of the promises if any of them 401's. (Note, this requires a library like Bluebird that has cancellation semantics, or some other form of augmentation of your promise/request.)
dispatch an action or route-change to trigger the re-login window
anyFail(error) => {
if (error.status === 401) {
allCallPromises.forEach((item)=> {item.cancel();});
reLogin();
}
}
Then, I'd be inclined to let your relogin component worry about re-firing that same complex action again, to issue all the calls.
However, should your suite of API calls be somehow variable or context-specific, you could cache on the store the ones you need, from inside the anyFail handler. Have a reducer where you can stash an actionPendingReLogin. Compose an action that will re-fire the same calls as last time, and then dispatch it:
dispatch(createAction('CACHE_RELOGIN_ACTION`, actionObjectToSaveForLater));
(Or, just cache whatever action-creator you used.)
Then, following successful relogin, you can:
const action = store.getState('actionPendingReLogin');
dispatch(action);
// or:
const actionCreator = store.getState('actionPendingReLogin');
dispatch(actionCreator());
Oh: and in your allSuccess handler you'd simply dispatch the results of the async calls.