I'm using axios to do a post in my action creator. My action creator takes in an array of objects and posts it to an express server.
I'm trying to set the payload to be the response of the server but I realize that the response occurs after the action has been created.
export function sendOrders (data : Order[] ){
console.log("gets inside sendOrders action creator")
var output : string;
axios.post('http://localhost:8081/', data)
.then(function (response) {
output = response.data;
console.log(output)
})
.catch(function (error) {
output = error;
console.log(output)
});
return {
type: SEND_ORDERS,
payload : output
}
}
As a result, my reducer returns "undefined". Does anyone know how I can work around this?
Action creators are synchronous. That being said, there are redux plugins, like redux-thunk and redux-saga that allow an action creator to be asynchronous, which would let you emit an action after the Promise completes.
Example using redux-thunk:
export function sendOrders (data : Order[]) {
return (dispatch) => {
var output : string;
return axios.post('http://localhost:8081/', data)
.then(function (response) {
output = response.data;
console.log(output)
dispatch({
type: SEND_ORDERS,
payload: output
});
})
.catch(function (error) {
output = error;
console.log(output)
});
};
}
So essentially, your action creator returns a Promise-returning function which is passed dispatch and getState so you can asynchronously read state and dispatch actions. In order to use this example, you have to add the plugin. From the sample in the documentation:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Related
I have 2 actions that make GET requests and save the response in the Vuex store. The first action getVersion() gets the most recent version of the game and that version is required in order to make the second GET request. Right now I've hard coded the version in the second action, however, my goal is to concatenate it inside the URL.
Sadly I'm not sure how to access it from inside the function. Console.log(state.version) returns null for some reason even though it shouldn't be. I call these functions from inside App.vue like this:
mounted(){
this.$store.dispatch('getVersion')
this.$store.dispatch('getChampions')
}
Vuex store
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
version: null,
champions: null
},
mutations: {
version(state, data){
state.version = data.version
},
champions(state, data){
state.champions = data.champions
}
},
actions: {
getVersion({commit}){
axios.get("http://ddragon.leagueoflegends.com/api/versions.json")
.then((response) => {
commit('version', {
version: response.data[0]
})
})
.catch(function (error) {
console.log(error);
})
},
getChampions({commit, state}){
axios.get("https://ddragon.leagueoflegends.com/cdn/9.24.1/data/en_US/champion.json")
.then((response) => {
commit('champions', {
champions: response.data.data
})
})
.catch(function (error) {
console.log(error);
})
}
},
getters: {
version: (state) => {
return state.version;
},
findChampion: (state) => (id) => {
let championId = id.toString();
let champion = Object.values(state.champions).find(value => value.key === championId);
return champion
}
}
})
With this part:
this.$store.dispatch('getVersion')
this.$store.dispatch('getChampions')
The second dispatch doesn't wait for the first one to finish. Meaning that it is firing before the first one has had a chance to finish getting the version.
You need to create a promise that should resolve before the second dispatch is called.
You could try doing it this way:
async mounted(){
await this.$store.dispatch('getVersion')
await this.$store.dispatch('getChampions')
}
or if you don't want to use async/await
this.$store.dispatch('getVersion').then(() => {
this.$store.dispatch('getChampions');
});
And in the action you should add return to the request (this is important):
return axios.get(...
dispatcher returns a promise
this.$store.dispatch('getVersion').then(()=>{
this.$store.dispatch('getChampions');
});
I am using Vuex/Axios to make GET requests to an API. When a component mounts, I am dispatching an action to the Vuex store and making the Axios GET request. In the Vuex action, the Axios GET request returns the response as expected but the response inside the component is returning undefined. What am I doing wrong?
axios/index.js
import axios from 'axios';
const API_URL = 'http://localhost:3000/api/v1/';
const plainAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
});
export { plainAxiosInstance };
Vuex module: store/modules/character.js. In this file response logs the correct response. The fetchCharacters event gets triggered in a component.
import { plainAxiosInstance } from '#/axios';
const characterModule = {
namespaced: true,
state: {
characters: []
},
mutations: {
SET_CHARACTERS(state, characters) {
state.characters = characters;
}
},
actions: {
async fetchCharacters({ commit }) {
await plainAxiosInstance
.get('/characters')
.then(response => {
let characters = response.data;
commit('SET_CHARACTERS', characters);
console.log(characters); <-- Logs the expected response data
return characters;
})
.catch(error => console.log('Failed to fetch characters', error));
}
},
getters: {}
};
export default characterModule;
I am then dispatching the Vuex action inside of a Vue component on mount:
<script>
import { mapState, mapActions } from 'vuex';
export default {
mounted() {
this.fetchCharacters()
.then(response => {
console.log('response', response);
// response is logging undefined here <----
})
.catch(error => {
console.log(error);
});
},
computed: mapState(['character']),
methods: mapActions('character', ['fetchCharacters'])
};
</script>
The console.log in modules/character.js logs the data as expected and then the response inside of the component logs undefined. I made sure to return the variable characters in the Vuex module. And I also made the Vuex action fetchCharacters async. So why is the response in the component returning undefined?
Thanks if you can help.
Change this:
async fetchCharacters({ commit }) {
await plainAxiosInstance
to this:
fetchCharacters({ commit }) {
return plainAxiosInstance
You can keep the async if you want but it won't make any difference.
In its present form the action will implicitly return a promise and that promise won't resolve until the request is complete. However there's nothing to tell it to resolve that promise to the desired value.
Instead of waiting for the promise inside the action you can just return that promise instead. Externally that won't make any difference as you'll just get back a promise either way but crucially that promise will resolve to the correct value once the request is complete.
I'm new to react / redux
When I doing the project in jquery
I will make some functions like this:
errorHandle (code) {
if(code = 1){
$('.popUpA').show()
}
...
}
callAPI (){
//Do AJAX call
//If Error , call errorHandle()
}
In the new project,
I use the axios to call API in the api helper
export function getDataList(){
//axios....
}
export function getData(){
//axios....
}
And I use the Store to trigger the show/hide popUp , I will use dispatch(showPopup()) and dispatch(showPopup(hide)) in component
But I want that if api function have error , will pass the response in to the errorHandler , then dispatch the showPopup. I don't have idea how to add this into the exported function.
Any suggestion or example?
this is my abstraction of axios request, I use it in services that are used with Redux:
import axios from 'axios';
import { API } from '../../constants';
import { revokeAuthAction } from ;
export const getAuth = () => {
// Auth logic
};
/**
* Create an Axios Client with defaults
*/
const client = axios.create({
baseURL: API.BASEURL,
headers: {
Authorization: getAuth(),
'Access-Control-Max-Age': 1728000,
// 'X-Authorization-JWT':
},
});
/**
* Request Wrapper with default success/error actions
*/
const request = (options) => {
const onSuccess = (response) => options.raw ? response : response.data;
// console.debug('Request Successful!', response);
// If options.raw is true, return all response
const onError = (error) => {
// console.error('Request Failed:', error.config);
if (error.response) {
if (error.response.status === 401) {
// console.error('Unauthorized');
store.dispatch(revokeAuthAction());
} else {
// Request was made but server responded with something
// other than 2xx
// console.error('Status:', error.response.status);
// console.error('Data:', error.response.data);
// console.error('Headers:', error.response.headers);
}
} else {
// Something else happened while setting up the request
// triggered the error
// console.error('Error Message:', error.message);
}
return Promise.reject(error.response || error.message);
};
return client(options)
.then(onSuccess)
.catch(onError); // in realtà non catcho un bel niente perchè ritorno Promise.reject e quindi il giro ricomincia
};
export default request;
There are more library to handle redux async action calls. I use redux-thunk another well known library is redux-saga. With redux thunk you will add a middleware to redux and this way you can create async action creators which return a function, and they can call other action creators depending of the result of the async call.
You add the middleware this way:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
And your action creator will be something like this:
export function requestDataList() {
return function (dispatch: Function, getState: Function) {
return getDataList().then(resp => {
dispatch(dataListReceived(resp.data));
}).catch(error => {
dispatch(showPopup(title, error.message));
});
};
}
So if your getDataList retrun an axios promise, on success it will call an action wit the result. On error it can call the error dialog.
I have a react + typescript application and I have an async api call done with axios. I want to test that async call using Jest + Enzyme.
This is what I have for my action
// load items callback
export function loadItemsSuccess(response) {
return {
type: LOAD_ITEMS,
items: response.data
};
}
// load items
export function loadItems() {
return function (dispatch) {
const authOptions = {
method: "GET",
url: "http://192.168.99.100:3000/items
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
json: true
};
return axios(authOptions).then(response => {
dispatch(loadItemsSuccess(response));
}).catch(error => {
console.log("Error loading items, error);
});
};
}
My reducer simple updates the store:
case LOAD_ITEMS:
console.log(action.items);
return action.items;
Unit testing the action should test whether the expected object is dispatched.
One way to do this is to use a combination of redux-mock-store and axios-mock-adapter.
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
// import any actions and types
const middleware = [thunk]
const mockStore = configureMockStore(middleware)
describe('loadItems', () => {
it('Dispatches LOAD_ITEMS', () => {
let mock = new MockAdapter(axios)
mock
.onGet('http://192.168.99.100:3000/items')
.reply(200, { data: 'mock' })
const store = mockStore({})
return store.dispatch(actions.loadItems())
.then(() => {
const actions = store.getActions()
expect(actions[0]).toHaveProperty('type', LOAD_ITEMS)
})
})
})
Reducers and the values in the store should be tested as a separate unit. We're just using the mockStore to dispatch the action and ensure the proper type is dispatched. MockAdapter mocks network requests. This way we can create tests around various network conditions such as 400s and timeouts.
Axios Mock Adapter
Redux Mock Store
I'm trying to use redux thunk to make asynchronous actions and it is updating my store the way I want it to. I have data for my charts within this.props by using:
const mapStateToProps = (state, ownProps) => {
const key = state.key ? state.key : {}
const data = state.data[key] ? state.data[key] : {}
return {
data,
key
}
}
export default connect(mapStateToProps)(LineChart)
Where data is an object within the store and each time I make an XHR call to get another piece of data it goes into the store.
This is the the async and the sync action
export function addData (key, data, payload) {
return {
type: ADD_DATA,
key,
data,
payload
}
}
export function getData (key, payload) {
return function (dispatch, getState) {
services.getData(payload)
.then(response => {
dispatch(addData(key, response.data, payload))
})
.catch(error => {
console.error('axios error', error)
})
}
}
And the reducer:
const addData = (state, action) => {
const key = action.key
const data = action.data.results
const payload = action.payload
return {
...state,
payload,
key,
data: {
...state.data,
[key]: data
}
}
}
In the tutorial I have been following along with (code on github), this seems like enough that when a piece of data that already exists within the store, at say like, data['some-key'] redux will not request the data again. I'm not entirely sure on how it's prevented but in the course, it is. I however am definitely making network calls again for keys that already exist in my store
What is the way to prevent XHR for data that already exists in my store?
Redux itself does nothing about requesting data, or only requesting it if it's not cached. However, you can write logic for that. Here's a fake-ish example of what a typical "only request data if not cached" thunk might look like:
function fetchDataIfNeeded(someId) {
return (dispatch, getState) => {
const state = getState();
const items = selectItems(state);
if(!dataExists(items, someId)) {
fetchData(someId)
.then(response => {
dispatch(loadData(response.data));
});
}
}
}
I also have a gist with examples of common thunk patterns that you might find useful.