Call action based on redux props received from another action - reactjs

This sounds a little odd (I'm new to react/redux) but let's say I have a component in which I call an action like so:
componentDidMount() {
this.props.getTask(this.props.match.params.id);
}
This action called here populates the redux state with some data related to the task (title, description etc.) What it also has is an id of another element which I need in order to call another action like so:
this.props.getSomethingElse(this.props.task.something._id);
The problem:
In componentDidMount I call the first action and I want to call the second action after I received the data from the first one because as I mentioned I need that id. How should I approach this? What's the best practice in this case?
EDIT: Redux action below.
//GET Task
export const getTask = id => dispatch => {
dispatch(setTaskLoading());
axios
.get(`/api/tasks/${id}`)
.then(res => {
dispatch({
type: GET_TASK,
payload: res.data
});
})
.catch(err =>
dispatch({
type: GET_TASK,
payload: null
})
);
};

React components should not orchestrate multiple redux calls. Calls should be dispatched because of user interaction, or life cycle (mount/unmount).
If you have actions that are dependent on other actions, you can combine them using a middleware (thunk for example) or sagas.
In this case, since getTask is a thunk, you can use it to dispatch multiple actions that can use the data returned from the async (axios) request:
//GET Task
export const getTask = id => dispatch => {
dispatch(setTaskLoading());
axios
.get(`/api/tasks/${id}`)
.then(res => {
dispatch({
type: GET_TASK,
payload: res.data
});
// getSomethingElse
dispatch({
type: GET_SOMETHING_ELSE,
payload: res.data.something.id
});
})
.catch(err =>
dispatch({
type: GET_TASK,
payload: null
})
);
};

Related

Redux dispatch function not triggered within catch block upon testing using Jest and Enzyme

I'm currently working on testing a react and redux course project using Jest and Enzyme. I'm running into an issue when I test the redux action methods. I have a function called requestRobots that performs an async operation to fetch data. Upon fetching and depending on the promise result, the action within try/catch block is dispatched. This logic is handled using the redux-thunk middleware. The issue I'm facing is that when I test this method using Jest, the dispatch within the catch block is not triggered although the error is captured and logged. I also checked if the catch block is working in the actual project and it does dispatch the action in the project when there is an error. However this is not the case when I test it using Jest. I have used redux-mock-store for setting up the mock redux store and apiCall used in the code snippet is just an abstraction of the fetch API call
Could someone please help me out in fixing this issue.
I have attached snippets of my test and action code and screenshot of the logs that I get on running the test.
action.test.js
it("Test request robots action when called - unsuccessful state", async () => {
const apiCall = jest.fn().mockReturnValue(Promise.reject("Not found"));
const store = mockStore();
await store.dispatch(actions.requestRobots(apiCall));
const action = store.getActions();
console.log(action);
expect(apiCall).toBeCalled();
expect(apiCall.mock.calls.length).toBeGreaterThan(0);
console.log(apiCall.mock);
});
action.js
export const requestRobots = apiCall => dispatch => {
dispatch({ type: REQUEST_ROBOTS_PENDING });
apiCall("https://jsonplaceholder.typicode.com/users")
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => {
console.log(error);
dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error });
});
};
Output logs output obtained after running the action.test.js file
Thanks in advance
return keyword is missing. use return keyword before api call like below
export const requestRobots = apiCall => dispatch => {
dispatch({ type: REQUEST_ROBOTS_PENDING });
return apiCall("https://jsonplaceholder.typicode.com/users")
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => {
console.log(error);
dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error });
});
};
if this solution doesn’t work use return keyword before catch block's dispatch like below
export const requestRobots = apiCall => dispatch => {
dispatch({ type: REQUEST_ROBOTS_PENDING });
return apiCall("https://jsonplaceholder.typicode.com/users")
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => {
console.log(error);
return dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error });
});
};

React Redux - Waiting for async api call to finish before next action is dispatched

I am working on a web app that uses React + Redux, with a backend using Django (DRF). I am using axios to send in my API request, which is asynchronous. The issue I am facing right now is that the actions dispatched do not wait for the API call to finish before the next action is dispatched. Below is my code
const mapDispatchToProps = dispatch => ({
success: id => {
dispatch(fetchSalesProject(id));
dispatch(createMessage('Requirement successfully updated!'))
}
})
fetchSalesProject action (axiosInstance is just a custom modification of axios call, the functionality is the same)
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
createMessage action
export const createMessage = (message) => {
console.log('message')
return {
type: CREATE_MESSAGE,
message: message,
};
};
When calling this.props.success (refer to mapDispatchToProps), the message is displayed before the api call response data is received (evident by the fact that console.log('message') runs before console.log('fetched data'))
I would want the data to be fetched from the api call before i run the createMessage action, is there any advise on how to accomplish that? I am new to React and especially Redux, so hope that you guys can point me in the right direction on how to accomplish that.
Also, can I check whether it is wrong to have a dispatch in the mapDispatchToProps, and also a dispatch within the action (refer to fetchSalesProject action). Would it cause any issues with performance or is it frowned upon to do so? Please advise me as I am quite confused with Redux.
Thanks all for reading through, all help is appreciated :-)
while you are dispatching from UI, you just sending an object towards reducer which in his turn will modify the state at the store and in the end of the process will re-render components that refer to props that changed. At the moment you are dispatching the first action, there is nothing that tells the component that it should wait before sending the next object to the reducer
So you have 2 options,
the first is at UI itself use componentDidUpdate or useEffect for run the second action after the first action reduced
componentDidUpdate(prevProps) {
if (prevProps.salesProject != this.props.salesProject)
dispatch(createMessage('Requirement successfully updated!'))
}
while I assume that dispatch(fetchSalesProject(id)); modify salesProject prop
Another way to do that, and in case you actually fine with that message and salesProject will gonna get together to the reducer, is to dispatch them in one action
export const fetchSalesProjectAndMessage = (id, message) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT_AND_MESSAGE,
payload: { data: res.data, message }
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
and at reducer payload.data either payload.message will refer to desired info
There is a better way of doing this that does not force you combine two action creators. When your thunk action creator returns a promise then you can wait for it. Your current code did not return the axios promise but if you do return it you can do the following:\
const mapDispatchToProps = (dispatch) => ({
success: (id) => {
dispatch(fetchSalesProject(id)).then(() =>
dispatch(//wait for fetchSalesProject to finish
createMessage('Requirement successfully updated!')
)
);
},
});
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action');
return axiosInstance //you did not return anything here
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data');
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => {
dispatch(
returnErrors(err.response.data, err.response.status)
);
//return rejected promise here
return Promise.reject(err);
});
};

Call multiple dispatch after getting axios response Redux

I need to call multiple dispatches after getting the response from axios how can I do, it the current way I'm doing is only allowing me to send one dispatch and when trying to fire the second dispatch it gives and error saying that the res is not found.
export const getItem = (id) => dispatch =>{
dispatch(setLoading);
axios.get(`http://localhost:8081/getItem?id=${id}`)
.then(
res => dispatch({
type: GET_ITEM,
payload: res.data
})
)
}
I need to fire a secondary dispatch after getting the response, the functionality of the secondary dispatch, I need to this after getting the return from axios is because a unique email and id is stored in the response, so I need to get the id, from the response to fire the secondary call.
export const getUserItem = (payload) => dispatch =>{
dispatch(setItemsLoading);
axios.get(`http://localhost:8081/getUserItems?email=${payload.email}&id=${payload.id}`)
.then(
res => dispatch({
type: GET_USER_ITEM,
payload: res.data
})
)
}
Is there a way to dispatch 2 after a response is received from axios?
With arrow syntax you need to add brackets if you want to do more than a single statement:
export const getItem = (id) => dispatch =>{
dispatch(setLoading);
axios.get(`http://localhost:8081/getItem?id=${id}`)
.then(
res => {
dispatch({
type: GET_ITEM,
payload: res.data
})
//second dispatch
dispatch({
type: GET_ITEM,
payload: res.data
})
}
)
}

react-redux re-rendering on componentDidMount

I'm using React with Redux with multiple reducers.
I have a component in which I want to fetch data from multiple reducers but each time I make a call to action it re-renders the component (obviously...)
async componentDidMount() {
await this.props.getBooksNamesAsync();
await this.props.getAuthorsNamesAsync();
await this.props.getSubscribersAsync();
this.props.setFilter(
this.props.book.bookNames,
this.props.author.authorNames,
this.props.subscriber.subscriberNames
);
}
this.props.getBooksNamesAsync() is action on book.
this.props.getAuthorsNamesAsync() is action on author.
this.props.getSubscribersAsync() is action on subscriber.
my question is what the best practice for such issue ?
Is re-rendering the component every action is legitimate ?
Should I write another action that contains all these actions in one place ?
which is quiet code duplication and I prefer to avoid it...
or any other options...
The component rerenders every time there is state change... You can and you should... Here is an example from an old project:
First action creator:
export const fetchPosts = () => async (dispatch) => {
const response = await axios.get('/posts');
dispatch({ type: 'FETCH_POSTS', payload: response.data });
};
Second action creator:
export const fetchUser = id => async dispatch => {
const response = await axios.get(`/users/${id}`);
dispatch({ type: 'FETCH_USER', payload: response.data });
};
And both combined: (note, it's making use of lodash but you do not have to...)
export const fetchPostsAnUsers = () => async (dispatch, getState) => {
await dispatch(fetchPosts());
const userIds = uniq(map(getState().posts, 'userId'));
userIds.forEach(id => dispatch(fetchUser(id)));
};
This was a use case to cut down on the number of calls made to the api but the same holds true for your use case...

Redux isn't holding the data after callback fucntion? [duplicate]

This question already has answers here:
Handling async request with React, Redux and Axios?
(4 answers)
Closed 5 years ago.
import axios from 'axios';
export const DO_LOGIN = 'do_login';
export function doLogin(values, callback){
console.log(values);
const request = axios.post(`http://localhost:80/auth/signIn`, values)
return{
type: DO_LOGIN,
payload: request
}
}
after calling my function doLogin it is executing the request and callback as specified and working good after that the request isn't holding my data anymore and showing it as undefined where I look at in my console?
I am unsure what you are trying, but I would recommend to additionally use redux-thunk middleware, to do that:
const doLogin = values => dispatch => axios.post(…)
//not sure if one needs that for axios,
//but for fetch that is required
.then(res => res.json())
.then(json => dispatch({
type: DO_LOGIN,
payload: json
}));
That way the action dispatch is actually refered until the request is finished and the resulting data is dispatched to the store.
That way you also handle errors like so:
const doLogin = values => dispatch => … //as above
//…
.catch(error => dispatch({
type: DO_LOGIN_ERROR,
error: true,
payload: error
}))
Additionally you can dispatch more actions so indicate the start of the request like so:
const doLogin = values => dispatch => {
dispatch({
type: DO_LOGIN_START
});
return axios.post(…)
.then(res => res.json())
.then(json => dispatch({
type: DO_LOGIN_SUCCESS,
payload: json
}))
.catch(err => dispatch(/*as above*/))
}
Please note that the action creator returns the promise, so in other places, where you trigger the action you can do:
doLogin({…}).then(data => callBack());
If you follow that way, it is much easier to in cooperate with redux-form, if you are using that for your forms, what I would recommend to use, too.
You should be aware, that the within the response object, its data is exposed as stream. That means, you cannot read the data twice. Once res.json() or res.body() is called, the data of the response is »consumed« and cannot be accessed from the response object again.

Resources