I'm using Redux (+ thunk) to fetch data from my API. I implemented a Data Fetcher component that calls all the actions and once done, dispatches a LOADED. In my actual main component where I render content, I wait until isLoaded flag in the props is set to true.
Here's the method in the data fetcher:
const fetchData = () => {
if (isAuthenticated) {
const p = [];
p.push(fetchDataSetOne());
p.push(fetchDataSetTwo());
Promise.all(p).then( () => setHasLoaded() );
}
}
Each of those fetch methods returns an axios promise, in which I dispatch once retrieved like so:
export const fetchDataSetOne = () => dispatch => {
return axios.get(`${API_URL}/dataSetOne`)
.then(res => {
dispatch({
type: FETCH_ALL_DATA_SET_ONE,
payload: res.data.docs
});
});
};
In my component's render function I only render the content when loaded (which is set by the LOADED dispatch from the setHasLoaded action) like so:
{ hasLoaded && <MyContent> }
Even though I "wait" for the actions to finish (= Promise.all), my variable hasLoaded is set to true before the fetched data is set. Can anybody help?
The problem is you return a function NOT a promise.
This resolves immediately.
See working code sample
export const fetchData2 = dispatch => () => {
dispatch({type: 'START_FETCH'})
const p = [
fetchDataSetOne(dispatch),
fetchDataSetTwo(dispatch)
];
Promise.all(p).then((res) => setHasLoaded(res));
};
// this returns a promise AFTER it calls an action
const fetchDataSetOne = dispatch => {
return axois.get(`${API_URL}/dataSetOne`).then(res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res.data.docs
});
});
};
This resolves after both promises are resolved, but the state updates after each promise is resolved. To update state after all promises resolve, try this:
export const fetchData3 = dispatch => () => {
dispatch({ type: "START_FETCH" });
const p = [
axois.get(`${API_URL}/dataSetOne`),
axois.get(`${API_URL}/dataSetTwo`)
];
Promise.all(p).then(callActions(dispatch));
};
const callActions = dispatch => res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res[0].data.docs
});
dispatch({
type: "FETCH_ALL_DATA_SET_TWO",
payload: res[1].data.docs
});
setHasLoaded(res);
};
Related
I am trying to fetch some data from the API and setting the data in my redux store. However, when I try to do some operation with the data from the redux store that variable is empty. I have used await but it seems it does not work. However, after useEffect the redux store(api data) data is visible and can do operations on it. Any help is appreciated. Please note I need to access the redux store field not just get the returned data from the function. Accessing the redux store field is important. Here is my code:
useEffect(() => {
async function loadData() {
const startingDateYear = moment();
const eventDates = generateDatesForYear(startingDateYear.year().toString());
await dispatch(fetchData('0009', eventDates[0], eventDates[1]));
}
loadData();
console.log(event.myDataArray) // event is a reducer and myDataArray is the field. It can be accessed outside the function with data incorporated but within useEffect I am not able to use the freshly fetched data.
return {};
}, []);
export const fetchData = (p1, p2, p3) => {
return async dispatch => {
const path = `dataFromAPIURL`;
try {
dispatch({
type: FETCH_STARTED,
});
const myDataArray = await RestService.get(path);
dispatch({
type: FETCH_FINISHED,
});
dispatch({
type: UPDATE_REDUCER_STATE,
payload: myDataArray,
});
return myDataArray;
} catch (error) {
// TODO: error handling
dispatch({ type: FETCH_ERROR, payload: error });
}
};
};
You need a second useEffect with dependency event.myDataArray
useEffect(() => {
async function loadData() {
const startingDateYear = moment();
const eventDates = generateDatesForYear(startingDateYear.year().toString());
await dispatch(fetchData("0009", eventDates[0], eventDates[1]));
}
loadData();
}, []);
useEffect(() => {
console.log(event.myDataArray);
}, [event.myDataArray]);
I was just going through some react-redux code online and basically i came across the following set of code , which is basically a js file full of ACTIONS , just to give some context a combination of redux and redux-thunk is being used here:
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
// Action Creator to get the config object from the API
export const getConfig = () => async dispatch => {
const res = await tmdbAPI.get('/configuration');
dispatch({
type: TYPES.GET_CONFIG,
payload: res.data,
});
};
I am a bit confused as to why is getConfig function is being wrapped inside a dispatch ?
if you don't want, don't wrap that
like this
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
dispatch(await getConfig());
dispatch({ type: TYPES.REMOVE_LOADING });
};
//this is not using redux-thunk
// Action Creator to get the config object from the API
export const getConfig = async () => {
const res = await tmdbAPI.get('/configuration');
return {
type: TYPES.GET_CONFIG,
payload: res.data,
};
};
Important
but there are so many reasons why wrap the actions by dispatch.
Here is one example.
when you want get multiple data in one action step by step
export const getData = () => async dispatch => {
dispatch({
type: DATA_LOADING_START
});
try {
const res = await tmdbAPI.get('/url1');
dispatch({
type: DATA1_LOADED,
payload: res.data,
});
const res = await tmdbAPI.get('/url2');
dispatch({
type: DATA2_LOADED,
payload: res.data,
});
} catch (err) {
// handle error
}
dispatch({
type: DATA_LOADING_END
});
};
//this is using redux-thunk
#AlexanderSolonik
Question: Why wrap actions by dispatch?
because dispatch() sends the action result to the reducer.
Redux thunks are just redux actions that can perform side effects. So
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
Is just an async init function which when called performs the steps in a synchronous manner.
The key is that thunks can dispatch other thunks/actions/etc so the init thunk is just dispatching the getConfig() action which is itself async so the next step of the init function won't be performed until the config API call finishes (possibly because some other code depends on it)
I'm writing a function to send API requests. When I get the response from that API I want to dispatch redux action if the user has called my function in dispatch or just do nothing if not. I'm using redux thunk.
Right now I've written two separate methods for this.
This does not dispatch after getting the response from the API, just return the Promise.
const getAnimalsList = () => return axios.request({url: 'api.domain.com/animals'});
This function will be called as usual functions.
It dispatches an action something after getting a response from the API
const getAnimalsList = () => (dispatch, getState) => {
axios.request({url: 'api.domain.com/animals'}).then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
This function will be called inside dispatch as dispatch(getAnimalsList())
Now what I want is to know in a single function whether it was called inside the dispatch or just called normally.
example:
const getAnimalsLis = () => {
let prom = axios.reques({url: 'api.domain.com/animals});
if(function_is_called_inside_dispatch){
return dispatch => {
prom.then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
}
else return prom;
}
This is a wrong way to do things. There's no way to detect that the function was called like dispatch(getAnimalsList()) or just getAnimalsList(). Due to operator precedence, it has already been called as getAnimalsList() when an action provided to dispatch. The only way would be to call it differently, dispatch(getAnimalsList(CALLED_AS_AN_ACTION)).
A correct way is to not mix functions that serve different purposes. The code can be made DRYer than it is, getAnimalsList function already contains common code that could be extracted otherwise:
const getAnimalsList = () => return axios.request({url: 'api.domain.com/animals'});
const getAnimalsListAction = () => (dispatch, getState) => {
return getAnimalsList().then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
Please do not define getState if you are not planning on using it:
const getAnimalsList = () => (dispatch, getState) => {
axios.request({url: 'api.domain.com/animals'}).then(
res => dispatch({type: 'RESPONSE RECEIVED', data: res}),
err => dispatch({type: 'ERROR', err})
);
}
And also use the async/await syntax, its more explicit for you as to what is going on:
export const getAnimalsList = () => async dispatch => {
const response = await jsonAnimals.get("/animals");
dispatch({ type: "RESPONSE_RECEIVED", payload: response.data });
};
Then create a folder/file system like so: apis/jsonAnimals.js:
Place your Axios code in there:
import axios from 'axios';
export default axios.create({
baseURL: 'http://api.domain.com'
});
Okay, now you have a nice clean Redux application, makes it easier on the eyes, easier to debug.
Now if you want to test it in the console then you could do
export const testGetAnimalsList = () => async (dispatch, getState) => {
await dispatch(getAnimalsList());
console.log(getState().animals);
};
export const getAnimalsList = () => async dispatch => {
const response = await jsonAnimals.get("/animals");
dispatch({ type: "RESPONSE_RECEIVED", payload: response.data });
};
I'm trying to mock this axios call:
export const fetchCountry = (query) => {
return dispatch => {
dispatch(fetchCountryPending());
return axios.get(`${process.env.REACT_APP_API_URL}/api/v1/countries/?search=${query}`)
.then(response => {
const country = response.data;
dispatch(fetchCountryFulfilled(country));
})
.catch(err => {
dispatch(fetchCountryRejected());
dispatch({type: "ADD_ERROR", error: err});
})
}
}
Which on a successful call, should dispatch both action creators fetchCountryPending() and fetchCountryFullfilled(country). When I mock it like so:
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
// Async action tests
describe('country async actions', () => {
let store;
let mock;
beforeEach(function () {
mock = new MockAdapter(axios)
store = mockStore({ country: [], fetching: false, fetched: true })
});
afterEach(function () {
mock.restore();
store.clearActions();
});
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', () => {
const query = 'Aland Islands'
mock.onGet(`${process.env.REACT_APP_API_URL}/api/v1/countries/?search=${query}`).replyOnce(200, country)
store.dispatch(countryActions.fetchCountry(query))
const actions = store.getActions()
console.log(actions)
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});
});
The second expect fails and console.log(actions) only shows an array with the one action, but it should contain both actions, fetchCountryPending and fetchCountrySuccess. When I log ('dispatched'), it shows the second action is getting dispatched in the terminal.
Can you try making your it block async and dispatch the action. I believe the tests are running before your get requests return the value
I couldn't get a then(() => {}) block to work but I was able to await the function and make it async:
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', async () => {
const query = 'Aland Islands'
mock.onGet(`${process.env.REACT_APP_API_URL}/api/v1/countries/?search=${query}`).replyOnce(200, country)
await store.dispatch(countryActions.fetchCountry(query))
const actions = store.getActions()
console.log(actions)
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});
});
Let's assume I have a component called BookOverview that displays details of a book.
I'm getting data with an action:
componentDidMount() {
this.props.getBook(areaId);
}
And then I get the data with axios:
export const getBook = () => async dispatch => {
const res = await axios.get(
`${API}/${ENDPOINT}`
);
dispatch({
type: GET_BOOK,
payload: res.data
});
};
How shall I change this code to:
if redux store already have the book loaded - return it
if no book is present in the store - call the relevant API?
What is the best practise to achieve that please?
You can have the getState inside your async action creator like this:
export const getBook = () => async (dispatch, getState) => {
if(!getState().book /* check if book not present */) {
const res = await axios.get(
`${API}/${ENDPOINT}`
);
dispatch({
type: GET_BOOK,
payload: res.data
});
} else {
dispatch({
type: GET_BOOK,
payload: getState().book
});
}
};
For More Async Actions-Redux
You can try it this way:
componentDidMount() {
if(this.props.book==null){
this.props.getBook(areaId);
}
}
I assumed that you have a property called book in your props. that populates from the particular reducer.
You have to subscribe the particular reducer to get the this.props.book - This gives the value that you have in your store.