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.
Related
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 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
We're having a hard time figuring out how context (or specifically { commit } is handled in chained promises with transpiled ES6 code. Below is one example of a Login action that authenticates and then subscribes using RxJS to the user as a stream. We need to commit several mutations throughout the process, but keep getting commit is not a function errors.
Does anyone know of or have an example of something like this or can anyone provide any basic guidelines on where and how context/commit are handled in this scenario - e.g. when can ES6 be used vs not, and/or where is context hoisted or not (if at all), and or is there a simpler approach to all this like maybe wrapping things all in a master promise? Since we need to potentially commit at each step in the promise chain, we cannot see how some of this could work:
const actions = {
login ({commit}, creds) { // need to commit here
commit('toggleLoading')
api.authenticate({
strategy: 'local',
...creds
})
.then(function (result) {
return api.passport.verifyJWT(result.accessToken)
})
.then(function ({commit}, payload) { //need to commit here
console.log(commit)
return api.service('users').get(payload.userId)
.subscribe(commit('setUser', user)) // need to commit here - but commit is not a function error
})
.catch(function ({commit}, error) {
commit('setErr', `ERROR AUTHENTICATING: {$err.message}`) // need to commit here but commit is not a function error
commit('toggleLoading')
})
}
All of the examples we find are way simplistic and only show one commit per action (or maybe 2 wrapped in an if). Any help or feedback is appreciated!
Firstly, the callback functions in .then and .catch take a single argument, you've coded two ... however, commit from the login arguments is still in scope, so it's quite simple to fix
Your code can be simplified as follows
const actions = {
login ({commit}, creds) {
commit('toggleLoading');
api.authenticate({strategy: 'local', ...creds})
.then(result => api.passport.verifyJWT(result.accessToken))
.then(payload => api.service('users').get(payload.userId).subscribe(commit('setUser', user)))
.catch(function (error) {
commit('setErr', `ERROR AUTHENTICATING: ${error.message}`);
commit('toggleLoading');
});
}
Note: you have {$err.message} in the .catch, whereas I beleive that should be ${error.message}
I'm learning React, along with pretty much all the necessary technology around it all at once - so I often get tripped up by things I should probably know already.
I've encountered a problem when it comes to error handling my async events. I've scoured the web and nothing really answers exactly what I'm looking for.
I'm currently using redux with redux-promise-middleware to handle the async actions, like this:
export function myFunc() {
return {
type: FETCH_FUNC,
payload: new Promise((resolve, reject) => {
fetch ('some/url/location/from/which/to/fetch')
.then( response => {
if (!response.ok){
throw new Error(response);
}
resolve(response.json());
}).catch(error => {
reject(error);
}),
})
};
}
There are two things here: first, the code works just fine when no errors are present. However, when I purposely create an error in the code the correct methods are firing but I still end up with the following error in my console:
Uncaught (in promise) Error: [object Response]
Should the .catch(...) block not be handling this? What am I missing? Should I be getting this anyway? If so, why?
Secondly, I've read that wrapping the fetch inside a new Promise is an anti-pattern, and there was an almost-hint that this may be what's causing problems here. All the examples I've come across use it in this fashion. What's the alternative? How do I fire the resolve/reject to dispatch the next actions without the wrapper?
Any help will be greatly appreciated. Thanks masters of the web.
-------------EDIT 1----------------
From the official redux-promise-middleware github examples, they have the following code:
export default function request(url, options) {
return new Promise((resolve, reject) => {
if (!url) reject(new Error('URL parameter required'));
if (!options) reject(new Error('Options parameter required'));
fetch(url, options)
.then(response => response.json())
.then(response => {
if (response.errors) reject(response.errors);
else resolve(response);
})
.catch(reject);
});
}
It seems to intention with the middleware is to wrap fetch inside a new Promise and catching any rejects. If anyone has a working alternative way of implementing this using redux-promise-middleware, or can elaborate on why its following this pattern that would be greatly appreciated.
-------------EDIT 2----------------
Not sure what the intended way of implementing this is or how to avoid the Uncaught error in the promise. Simply calling Promise.reject(...) results in an uncaught error unless you include error handling functions: Promise.reject(...).then(() =>{...}, error => {...}). Including this with the middleware results in the rejected action never being dispatched. I've moved away from redux-promise-middleware till I can find a suitable fix and/or implementation.
I guess what you are getting is the expected result and this is mentioned clearly in the middleware documentation:
The middleware dispatches rejected actions but does not catch rejected
promises. As a result, you may get an "uncaught" warning in the
console. This is expected behavior for an uncaught rejected promise.
It is your responsibility to catch the errors and not the
responsibility of redux-promise-middleware.
But if you ask about best practices this is what i ended up doing from long time ago and it's working perfectly with me:
1- For some promises you can do as mentioned in the documentation:
dispatch({
type: 'FOO_ACTION',
payload: new Promise(() => {
throw new Error('foo');
})
}).catch(error => {
// catch and handle error or do nothing
});
2- To catch all rejected promises globally add this middleware before the redux-promise-middleware as follow:
/**
* a utility to check if a value is a Promise or not
* #param value
*/
const isPromise = value => value !== null && typeof value === 'object' && typeof value.then === 'function';
export default () => {
const middleWares = [];
// global error middleware
middleWares.push(() => next => action => {
// If not a promise, continue on
if (!isPromise(action.payload)) {
return next(action);
}
/**
* include a property in `meta and evaluate that property to check if this error will be handled locally
*
* if (!action.meta.localError) {
* // handle error
* }
*
* The error middleware serves to dispatch the initial pending promise to
* the promise middleware, but adds a `catch`.
*/
if (!action.meta || !action.meta.localError) {
// Dispatch initial pending promise, but catch any errors
return next(action).catch(error => {
if (config.showErrors) { // here you can decide to show or hide errors
console.log(`${action.type} unhandled rejection caught at middleware with reason: ${JSON.stringify(error.message)}.`);
}
return error;
});
}
return next(action);
});
// middleware
middleWares.push(thunk);
middleWares.push(promise());
middleWares.push(logger());
return applyMiddleware(...middleWares);
}
i guess this is exactly what you are looking for ;)
Extra I highly recommend axios over fetch for the following reasons:
the axios module automatically reject the promise if the request has an error code which is something you need to keep manually handle in fetch
in axios you can create instance with default base-url,header,interceptors ...
in axios you can cancel any previous request using a token this is extremely useful specially for autocomplete and chat applications
also axios internally automatically switch between xhr and http modules to perform the ajax request based on the environment (NodeJs or Browser), i personally used the same redux actions in electron, nodejs, browser and react-native and it's all working fine
Following up on caisah 's comment, get rid of the indirection. You can resolve or reject a promise by simply resolving or rejecting with a new promise object
export function myFunc() {
return {
type: FETCH_FUNC,
payload: fetch ('some/url/location/from/which/to/fetch')
.then(response => {
if (!response.ok){
throw new Error(response);
}
return Promise.resolve(response.json());
}).catch(error => {
return Promise.reject(error)
}),
})
};
}
myFunc().payload.then(json => /* do stuff with json*/)
P.S the returns may be redundant.
I’ve used "Catching Errors Globally" presented in "Catching Errors Thrown by Rejected Promises", as shown, when calling applyMiddleware the errorMiddleware should be before the promiseMiddleware. To filter the action types where to apply this middleware i've preferred a regex:
This is the store creation:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import errorMiddleware from './errorMiddleware';
import adultosReducer from './adultosReducer';
const rootReducer = combineReducers({
adultosReducer
});
const composeStoreWithMiddleware = applyMiddleware(errorMiddleware, promiseMiddleware())(
createStore
);
export default composeStoreWithMiddleware(rootReducer);
This is the error middleware:
import isPromise from 'is-promise';
import _ from 'lodash';
const tiposAction = /^ADULTO/i;
export default function errorMiddleware() {
return next => action => {
// If not a promise, continue on
if (!isPromise(action.payload)) {
return next(action);
}
console.log('errorMiddleware: action.type', action.type);
if (action.type.match(tiposAction)) {
// Dispatch initial pending promise, but catch any errors
return next(action).catch(error => {
console.log('catching action', error);
return error;
});
}
return next(action);
};
}
That way you show gently to the user the error because the rejected action is dispatched without the Unhandled promise. And of course there is no need to add redux-thunk.
I'm writing a redux application using thunk middleware and experienced an issue regarding promises inside the async actions.
db.getLoggedInUser() returns a promise (Bluebird) that is fulfilled with a user object or a null (corresponding to the current logged in user).
If the user is logged in, I would like for fetch some relevant data, where userActions.fetchUserInfo is another async action (like initializeApp).
In the beginning of the process I'm dispatching a 'start_loading' action and after the all process is done - I'm dispatching a 'stop_loading' action.
export const initializeApp = () => dispatch => {
dispatch(loadingActions.startLoading());
return db.getLoggedInUser()
.then((user) => {
if (user) {
return dispatch(userActions.fetchUserInfo(user.id));
}
})
.catch(dbError => dispatch(errorActions.reportError(dbError.message)))
.finally(() => dispatch(loadingActions.stopLoading()));
};
When dispatching initializeApp() I'm getting the following error:
db.getLoggedInUser(...).then(...).catch(...).finally is not a function
I'm getting this error regardless the resolved value of getLoggedInUser().
Anyone has an idea?