React Redux thunk - Chaining dispatches - async-await

Currently i'm building an application that is heavily dependant on API calls. The api calls are done within Redux actions with Thunk middleware like so:
export const brand_fetchAll = () => {
return dispatch => {
fetch(apiURL+'brand')
.then(response => {return response.json();})
.then(content => {
dispatch({
type: 'BRAND_STORE_ALL',
content
})
})
.catch(error => console.log(error))
}}
In my component, i'm first fetching the data through separate actions. After that i'm opening up an editor:
// A component method
editModeOn(){
// Fetch data
this.props.dispatch(campaign_fetchAll());
this.props.dispatch(brand_fetchAll());
// Open editor
this.props.dispatch(page_editModeOn());
}
Right now the editor opens before the api calls have completed, so no data is being shown. It's possible to chain the dispatches within the actions, but i want to keep the modularity, so i don't have to create hundreds of custom API calls. Ideally what i want is to chain them using something like promises:
// A component method
editModeOn(){
this.props.dispatch(campaign_fetchAll().then(brand_fetchAll()).then(page_editModeOn());
}
Unfortunately i didn't yet get that to work. I hope someone can help me out. If you need more information i'm happy to hand it over. Better ideas are also very welcome :)
Thanks in advance!

Would a callback function be an option for you?
So update your code to be;
export const brand_fetchAll = (callback) => {
return dispatch => {
fetch(apiURL+'brand')
.then(response => {return response.json();})
.then(content => {
dispatch({
type: 'BRAND_STORE_ALL',
content
});
callback();
})
.catch(error => console.log(error))
}}
// A component method
editModeOn(){
// Fetch data
this.props.dispatch(campaign_fetchAll());
this.props.dispatch(brand_fetchAll(() => {
// Open editor
this.props.dispatch(page_editModeOn());
}));
}
You are chaining the callback onto the end of the api call success, however, you are not tightly coupling what it is as you are passing this in depending on the usage.

Related

Get vuex store state after dispatching an action

I'm creating a chat application in Laravel 6 + Vue + Vuex. I want make a call to vuex store and get a state after a dispatch actions is complete and then I want to do some processing on that state in my vue component.
In ChatWindow component
mounted: function () {
this.$store.dispatch('setContacts').then(() => {
console.log('dispatch called')
// I want to call the getter here and set one of the data property
});
}
action.js
setContacts: (context) => {
axios.post('/users').then(response => {
let users = response.data;
// consoled for testing
console.log(users);
context.commit('setContacts', users);
});
}
mutators.js
setContacts: (state, users) => {
state.contacts = users;
},
Please see the screenshot below. The then method of dispatch is running before setContacts in action.js.
I need to call the getter after completing dispatch action. (which will effectively set the contacts state). Then, I want to get the contacts through getContacts getter like this.
getters.js
getContacts: (state) => {
return state.contacts;
}
I also tried calling computed property in then in mounted and it didn't work. Also, shouldn't 'dispatch called' in mounted run after console.log of setContacts in action.js as it is in then method? Thanks!
Maybe you could wrap axios call inside another promise.
return new Promise((resolve, reject) => {
axios.post('/users')
.then(response => {
let users = response.data;
// consoled for testing
console.log(users);
context.commit('setContacts', users);
resolve('Success')
})
.catch(error => {
reject(error)
})
})
And then
this.$store.dispatch('setContacts')
.then(() => {
console.log('dispatch called')
console.log('getter ', this.$store.getters.contacts)
});
Let me know what happens. It was working for a small demo that I tried.

How to get a variable to bind from xhr call response in VueJS?

Ok I'm a beginner at VueJS and I'm just trying to do a simple XHR call and bind the json data response to my variable...
I have a component App.vue and this part of the template I want to show the results of the json. bpi is the name of the variable
<div id="simulationPoints">
<h2 className="logTitle">Full Log:</h2>
{{ bpi }}
</div>
then my script
export default {
name: 'App',
data: () => ({
bpi: []
}),
mounted: () => {
axios.get(`https://api.coindesk.com/v1/bpi/historical/close.jsonp?start=2011-01-01&end=2018-02-01`)
.then(response => {
this.bpi = response.data.bpi
})
.catch(e => {
this.errors.push(e)
})
}
}
This doesn't seem to work. I'm using Axiom to fetch the data and assign the response, and this is how all the examples I found online did it, but the array object I have is still empty and it doesn't render on the page. I don't know whats the issue here? A Vue expert please help :)
There are sorts of problem in your code.
First, don't use arrow function on options property or callback since arrow functions are bound to the parent context, this will not be the Vue instance as you’d expect.
Second, use return statement in your data function.
Third, use created hook for inserting data after instance is created. mounted hook is called for mutation after DOM is rendered.
export default {
name: 'App',
data: function() {
return {
bpi: []
}
},
created() {
axios.get(`https://api.coindesk.com/v1/bpi/historical/close.jsonp?start=2011-01-01&end=2018-02-01`)
.then(response => {
this.bpi = response.data.bpi
})
.catch(e => {
this.errors.push(e)
})
}
}

How can I check in a jest test if a thunk action within a thunk action creator has been dispatched?

Here's a generalized example:
// myActions.js
export const actionOne = () => (dispatch) => {
dispatch(actionTwo());
};
export const actionTwo = () => ({
type: 'SOME_TYPE',
});
I would like to test that actionTwo has been either called or dispatched, ideally without the test knowing anything about what is going on in actionTwo, because I have a different test that takes care of that.
I am using redux-mock-store to dispatch the tested action to a mocked store and calling store.getActions() to find out if the expected actions within the thunk action creator have been dispatched. I feel it is not the right way to go in this particular scenario because then the test would test more than it should. I really only want to know if actionTwo has been called at all.
I'm aware of spyOn and jest.mock, but I've been unable to use either to solve my problem. Here's what the generalized test looks like:
// myActions.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import * as actions from 'myActions';
const mockStore = configureMockStore([thunk]);
test('actionOne', () => {
const store = mockStore();
return store.dispatch(actions.actionOne()).then(() => {
// TODO: check if actions.actionTwo was called
});
});
test('actionTwo', () => {
const store = mockStore();
return store.dispatch(actions.actionTwo()).then(() => {
expect(store.getActions()).toEqual([{ type: 'SOME_TYPE' }]);
});
});
I'm grateful for any suggestions!
Took me a while, but I figured it out. It's not ideal (because it involves a small change to the tested code), but the closest to ideal that I could get.
// myActions.js
export const actionOne = () => (dispatch) => {
dispatch(exports.actionTwo());
};
export const actionTwo = () => ({
type: 'SOME_TYPE',
});
The important change is the exports.actionTwo(). That way, I make sure that I can overwrite the function's implementation from the outside (the test file) and the overwriting function will actually be called from within the imported file.
Now I can simply add something like the following to my test file:
beforeEach(() => {
actions.actionTwo = jest.fn(() => () => Promise.resolve());
});
actionTwo is now being mocked and I can use toBeCalledWith and other expectations on it. If I wish to test its actual implementation within the same test file, I can store it in a variable before calling beforeEach, like:
const actionTwo = actions.actionTwo;
And then in the test setup for its implementation, I can overwrite the mock calling
actions.actionTwo = actionTwo;
That's it. Now I can make sure to ignore all side effects from an exported function and test it as an actual unit.
It would be better to assert that two redux actions hit the store, not that actionOne calls the action creator.
Since all actions dispatched to the store must have an action type. Just make assertions about store.getActions():
test('actionOne', () => {
const store = mockStore();
return store.dispatch(actions.actionOne()).then(() => {
expect(store.getActions()).to.have.length(2);
expect(store.getActions()[0].type).to.equal('ACTION_ONE_TYPE');
// make additional assertions about the first action
expect(store.getActions()[1].type).to.equal('ACTION_TWO_TYPE');
});
});

Returning Promises from Vuex actions

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>

How to ajax call from RiotControl Store?

I have created APIs with Nodejs/Express.
Let say I can do GET request to localhost:8080/list and it returns JSON of my TODO list and I can POST to localhost:8080/list to create new to do list.
Then I use Riotjs + Riotcontrol for my Frontend website.
How do I request from todostore.js file?
This is the riotcontrol todostore.js file which I get from riotcontrol demo folder
Riotcontrol
// TodoStore definition.
// Flux stores house application logic and state that relate to a specific domain.
// In this case, a list of todo items.
function TodoStore() {
riot.observable(this) // Riot provides our event emitter.
var self = this
self.todos = [
{ title: 'Task 1', done: false },
{ title: 'Task 2', done: false }
]
// Our store's event handlers / API.
// This is where we would use AJAX calls to interface with the server.
// Any number of views can emit actions/events without knowing the specifics of the back-end.
// This store can easily be swapped for another, while the view components remain untouched.
self.on('todo_add', function(newTodo) {
self.todos.push(newTodo)
self.trigger('todos_changed', self.todos)
})
self.on('todo_remove', function() {
self.todos.pop()
self.trigger('todos_changed', self.todos)
})
self.on('todo_init', function() {
self.trigger('todos_changed', self.todos)
})
// The store emits change events to any listening views, so that they may react and redraw themselves.
}
You could do something like this in your TodoStore
self.on('todo_init', function() {
// Trigger loading here perhaps, then set loading = false when it's loaded
//self.trigger('set_loading', {value: true})
fetch('http://localhost:8080/list')
.then(response => response.json())
.then(function (json) {
self.todos = json
self.trigger('todos_changed', self.todos)
})
})

Resources