react-redux re-rendering on componentDidMount - reactjs

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...

Related

How to chain multiple dispatch actions one after the other in redux?

Im trying to chain multiple dispatch actions one for the other, by this order:
1.updateCart(dispatch,cartProducts ,loggedUser) - with redux-thunk.
after finishing fetching data, im dispatching actions by order:
2.dispatch(logoutReset()))
3.dispatch(logoutSuccess()))
4.then a refresh - window.location.reload())
but doesnt wait for dispatch to finish first before moving on so it keeps messing up.
i have tried many ways, with await or promises but didn't succeed.
i would like to learn from you guys, how to do it properly.
Component Navbar:
const handleLogout = async() => {
try{
await updateCart(dispatch,cartProducts ,loggedUser)
.then( ()=> dispatch(logoutReset()))
.then( ()=> dispatch(logoutSuccess()))
.then( ()=> window.location.reload());
}catch(err){
console.log(err)
}
};
Actions (updateCart) redux-thunk:
export const updateCart = async (dispatch, selectedProduct, loggedUser) => {
dispatch({ type: ActionTypes.UPDATE_CART });
try {
await userRequest.put(`carts/` + loggedUser.id, {
userId: loggedUser.id,
products: selectedProduct
})
} catch (error) {
console.log(error.response)
dispatch(returnErrors(error.response.data, error.response.status));
}
};
I have found a solution, posing if anyone will need it some day.
If you want to do something after dispatch finish, just put useEffect and the parameter that is updated after the dispatch, so after the dispatch and update you can do what you need to do.
very simple approach
const handleAddClick = () => {
dispatch(addToCart({selectedProduct:item,quantity}))
}
so after dispatch is finished cartProducts is updated.
useEffect(() => {
updateCart(dispatch,cartProducts ,loggedUser)
}, [cartProducts])

Redux Thunk vs Redux custom Middleware

I am new to redux. So after reading lots of tutorials.I understood that, redux need redux thunk to dispatch async actions by returning another function.But if I call http request inside custom middle-ware then
is it required redux thunk ?
is Redux custom middleware no side effects ? I mean no need to return another function.
If i use redux thunk then my action creator looks like this. This I understood
function incrementAsync() {
return (dispatch) => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
I have confusion in custom middle-ware.
https://blog.logrocket.com/managing-asynchronous-actions-in-redux-1bc7d28a00c6/
as per this blog
const httpMiddleware = store => next => action => {
if (action[HTTP_ACTION]) {
const actionInfo = action[HTTP_ACTION];
const fetchOptions = {
method: actionInfo.verb,
headers: actionInfo.headers,
body: actionInfo.payload || null
};
next({
type: actionInfo.type + "_REQUESTED"
});
fetch(actionInfo.endpoint, fetchOptions)
.then(response => response.json())
.then(data => next({
type: actionInfo.type + "_RECEIVED",
payload: data
}))
.catch(error => next({
type: actionInfo.type + "_FAILED",
payload: error
}));
} else {
return next(action);
}
}
they are not returning any dispatch function inside action. I know that store,next,action are the inner functions.
can any one help me to understand about this?
Thank you.
All redux-thunk is is a simple redux middleware that checks if your action is a function and acts accordingly. You can build it yourself in 5 minutes.
You can see that it deconstructs dispatch and getState from the store object, and then calls your action with them as parameters.
Have a look at it's source code.
So, your example code can look like this:
const httpMiddleware = store => next => action => {
if (action[HTTP_ACTION]) {
const actionInfo = action[HTTP_ACTION];
const fetchOptions = {
method: actionInfo.verb,
headers: actionInfo.headers,
body: actionInfo.payload || null
};
store.dispatch({
type: actionInfo.type + "_REQUESTED"
});
fetch(actionInfo.endpoint, fetchOptions)
.then(response => response.json())
.then(data => store.dispatch({
type: actionInfo.type + "_RECEIVED",
payload: data
}))
.catch(error => store.dispatch({
type: actionInfo.type + "_FAILED",
payload: error
}));
} else {
return next(action);
}
}
As you asked by point list I'm gonna reply by point list:
If i call http request inside custom middleware then is it required
redux thunk ?
Redux thunk is a middleware per se, it's recommended if you want to dispatch an action that make (let's say, for example) an AJAX call and dispatch two different action based on the result of that AJAX call, one if it fails and one if it succeeds.
is Redux custom middleware no side effects ? i mean no need to return another function.
If I'm understanding correctly: A middleware will simply take the action you dispatched and pass it through the chain, it'll let you do something before sending the action to the reducer "phase". What it'll do is entirely up to you. You need at least to do next(action) or block the action based on some logic, if needed.
Finally, a custom middleware like the one you posted is modifying the action you passed based on the response of an AJAX call, making it more of an "interceptor" than a simple middleware. Written this way, it's not dispatching a new action, it's more related to modifying the action you passed.
If you want to dispatch a new action based on the result of an ajax call, but without creating a middleware/interceptor, you could do it this way:
const postSomethingAction = postObject => {
return async dispatch => {
dispatch(postSomethingPending())
const response = await Api.postSomething(postObject)
if (response.message) {
dispatch(postSomethingError(response))
}
else {
dispatch(postSomethingSuccess(response.data))
}
}
}
In this example we're using Thunk do create an action that dispatch another action based on the result of Api.postSomething

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);
});
};

Fetching data from store if exists or call API otherwise in React

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.

Call action based on redux props received from another action

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
})
);
};

Resources