Correct way of error handling in React-Redux - reactjs

I want to understand what is more general or correct way of error handling with React-Redux.
Suppose, I have phone number sign up component.
That Component throws an error say if the input phone number is invalid
What would be a best way to handle that error?
Idea 1:
Create a component, which takes an error and dispatches an action whenever an error is passed to it
idea 2:
Since the error is related to that component, pass that error to a component (which isn't connected to redux i.e the error handler component won't dispatch the action)
Question: Can someone guide me on proper-way of error handling in React-Redux for large-scale app?

I would say that neither of your initial ideas capture the whole picture. Idea 1 is just a callback. If you want to use a callback: useCallback. Idea 2 will work and is preferable if you don't need to use redux. Sometimes you're better off using redux. Maybe you're setting form validity by checking none of the input fields have errors or something similar. Since we're on the topic of redux, let's assume that's the case.
Usually the best approach to error handling with redux is to have an error field in state that is then passed to an error component.
const ExampleErrorComponent= () => {
const error = useSelector(selectError);
if (!error) return null;
return <div className="error-message">{error}</div>;
}
The error component doesn't have to just display an error, it could also do side effects with useEffect.
How the error is set/unset depends on your application. Let's use your phone number example.
1. If the validity check is a pure function, it can be done in the reducer.
You would then set or unset the error field in response to phone number change actions. In a reducer built with a switch statement it could look like this.
case 'PHONE_NUMBER_CHANGE':
return {
...state,
phoneNumber: action.phoneNumber,
error: isValidPhoneNumber(action.phoneNumber) ? undefined : 'Invalid phone number',
};
2. If errors are reported by the backend, dispatch error actions.
Let's say you're sending the phone number to a backend that does validation before it does something with the number. You can't know if the data is valid on the client side. You'll just have to take the server's word for it.
const handleSubmit = useCallback(
() => sendPhoneNumber(phoneNumber)
.then(response => dispatch({
type: 'PHONE_NUMBER_SUBMISSION_SUCCESS',
response,
}))
.catch(error => dispatch({
type: 'PHONE_NUMBER_SUBMISSION_FAILURE',
error,
})),
[dispatch, phoneNumber],
);
The reducer should then come up with an appropriate message for the error and set it.
Don't forget to unset the error. You can unset the error on a change action or when making another request depending on the application.
The two approaches I outlined are not mutually exclusive. You can use the first to display locally detectable errors and also use the second to display server side or network errors.

I would use a formik with yup validation.
then, for server-side error i would use something like this:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Spinner } from "#blueprintjs/core";
export default ({ action, selector, component, errorComponent }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(action());
}, [dispatch, action]);
const DispatchFetch = () => {
const { data, isRequesting, error } = useSelector(selector());
if (!isRequesting && data) {
const Comp = component;
return <Comp data={data}></Comp>;
} else if (error) {
if (errorComponent) {
const ErrorComp = errorComponent;
return <ErrorComp error={error}></ErrorComp>;
}
return <div>{error}</div>;
}
return <Spinner></Spinner>;
};
return <DispatchFetch></DispatchFetch>;
};

It depends on what kind of error handling are you talking about. If it's only form validation handling then I don't think you need Redux for that - please read this article. If your error is only going to be "consumed" within that component, why send it to redux? You can easily use your local state for that.
On the other hand, if you want to show some kind of error notification to user indicating whether any HTTP call on site failed, you could benefit with redux by dispatching error from all parts of your application (or even generically your middleware)
dispatch({ type: 'SET_ERROR_MESSAGE', error: yourErrorOrMessage });
// simple error message reducer
function errorMessage(state = null, action) {
const { type, error } = action;
switch (type) {
case 'RESET_ERROR_MESSAGE':
return null;
case 'SET_ERROR_MESSAGE':
return error;
}
return state
}
You need to define how is your state going to be organized and whether you need to put some state in redux or just keep it in local state of your component. You could put everything in redux, but personally I'd say it's an overkill - why would you put state X in component Y if only component Y cares about that state? If you structure your code correctly you shouldn't have problem with moving that state from local to redux later on, if for some reason other parts of your app start to depend on that state.

I think about it like this, what should be state? And what should be derived from state?
State should be stored in redux, and derivations should be calculated.
A phone number is state, which field has focus is state, but whether or not it's valid, that can be derived from the values in state.
I would use Reselect to cache derivations and return the same results when the relevant state hasn't been modified.
export const showInvalidPhoneNumberMessage = createSelector(
getPhoneValue,
getFocusedField,
(val, focus) => focus !== 'phone' && val.length < 10 // add other validations.
)
You can then use the selector in mapStateToProps in all components that may care about this value, and you can use it in async actions as well. If the focus hasn't changed, or the value of the field hasn't changed, then no recalculation will occur, it'll return the previous value instead.
I'm adding the selected state check just to show how multiple pieces of state can come together to result in one derivation.
I personally try to approach things by keeping my state as small as possible. For instance, let's say you wanted to create your own calendar. Will you store every single day in state, or do you just need to know a couple things like the current year and month currently being viewed. With just these 2 pieces of state you can calculate the days to display on a calendar, and don't need to recalculate until one of them changes, and this recalculation will happen virtually automatically, no need to think about all the ways theat they could change.

Related

React userReducer init function not triggering on props update

I have a component which is a form which I use to create records in my database. I also want to be able to pass into this component values with which to populate the form, allowing me to then update my existing database records. Straightforward add/edit functionality from the same component.
The following code should explain how I am doing this. The media prop is an object containing the data. I have this data already in the parent element so setting the values here is fine and they pass thru without problem. However once the page is loaded the 3rd init argument of useReducer never re-triggers, and therefore my state cannot be overridden with the values passed down in the media prop. Is there a correct way to make the init function trigger when the props are updated, or is my issue architectural?
const MediaUploadForm = ({ media }) => {
const init = (initialState) => {
if (media) {
// ... here I extract the values I need and override the initialState where required
} else {
return initialState
}
}
const [formState, dispatch] = useReducer(
MediaFormReducer,
initialState,
init
)
So using the new React hooks features and keeping the component functional allows me to use useEffects() This is similar to using a componentDidUpdate type event. So the following code allows me to check for the status of a prop (media) and then dispatch an action that sets my redux state.
useEffect(() => {
if (media && id !== media.id) {
dispatch(loadMedia(media))
}
})
Thanks to #sliptype for pointing me in the right direction
Copying props into state is considered an anti pattern in React. Props changes do not trigger reinitialising state, as you have seen.
This is described in https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
From the recap it looks like the current suggested solution matches
Alternative 1: To reset only certain state fields, watch for changes in a special property (e.g. props.userID).
This is an alternative, rather than the recommendation.
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recap
Hope this link gives you more information around the topic, and the recommendations there help in future work.

Redux: request into success or error flow from Component (using redux-saga)

This is the one thing that I haven't found a standard solution to yet.
I have my store setup with redux-saga for side effect handling, I dispatch an action (that has async side effects) from a component, and now want the component to do something once the side effects are handled (for example navigate to another route/screen, popup a modal/toast or anything else).
Also I want to display a loading indicator or any errors on failure.
Before redux, this kind of flow was straight forward, it'd look something like this:
try {
this.setState({loading: true});
const result = await doSomeRequest();
this.setState({item: result, loading: false});
} catch (e) {
this.setState({loading: false, error: e});
}
With redux, I'd typically want to dispatch an action initiating the flow and have all related information in the store, to allow many components to listen to what is happening.
I could have 3 actions, 'requested', 'success', 'failed'.
In my component I would dispatch the requested action.
The responsible saga will then dispatch either the 'success' or 'failed' action upon handling 'requested'.
My Component will reflect on the changes.
But I haven't found out a standard way to figure out if the action has completed. Maybe the store hasn't updated as result of the async action (NO-OP, but loading state would still change I guess). But the action still succeeded, and I want to do something like navigate to another screen.
I really tried finding this kind of (seemingly common) scenario in the redux docs, redux-saga docs or Stackoverflow/Google, but no success.
Note: also with redux-thunk I think this behaviour is straight forward to achieve, since I can just .then on an async action dispatch and would receive the success action or the error action in catch (correct me if I'm wrong, never really used thunk). But I haven't seen the same behaviour achieved with redux-saga yet.
I've come up with 3 concrete solutions:
Most primitive solution, handling only the 'success'/'failed' actions from the component. Now this solution I am not a big fan of. In my concrete implementation there is no action that indicates that the async request has been started. The side effects are handled right there in the Component, instead of being abstracted away within a saga. Lots of potential code repitition.
Running a one time saga right before dispatching the request action, that races the 'success'/'failed' actions against each other and allows to react on the first occurring action. For this I've written a helper that abstracts the running of the saga away: https://github.com/milanju/redux-post-handling-example/blob/master/src/watchNext.js
This example I like a lot more than 1. since it's simple and declarative. Though I don't know if creating a saga during run time like this has any negative consequences, or maybe there is another 'proper' way to achieve what I'm doing with redux-saga?
Putting everything related to the action (loading, successFlag, error) into the store, and reacting in componentWillReceiveProps on action changes ((!this.props.success && nextProps.success)) means the action has completed successful).
This is similar to the second example, but works with whatever side effect handling solution you choose.
Maybe I'm overseeing something like the detection of an action succeeding not working if props hail in very fast and props going into componentWillReceiveProps will 'pile up' and the component skips the transition from non-success to success altogether?
Please feel free to have a look at the example project I've created for this question, that has the full example solutions implemented: https://github.com/milanju/redux-post-handling-example
I would love some input on the methods I use to handle the described flow of actions.
Am I misunderstanding something here? The 'solutions' I came up with were not straight forward to me at all. Maybe I'm looking at the problem from the wrong angle.
Are there any issues with the examples above?
Are there any best practice or standard solutions for this problem?
How do you handle the described flow?
Thanks for reading.
If I understand your question correctly, you want your component to take action based on actions fired off by your saga. This would typically happen in componentWillReceiveProps - that method is called with the new props while the old props are still available via this.props.
You then compare the state (requested / succeeded / failed) to the old state, and handle the various transitions accordingly.
Let me know if I've misinterpreted something.
I achieved the point of having an asynchronous action callback in a component using saga the following way:
class CallbackableComponent extends Component {
constructor() {
super()
this.state = {
asyncActionId: null,
}
}
onTriggerAction = event => {
if (this.state.asyncActionId) return; // Only once at a time
const asyncActionId = randomHash();
this.setState({
asyncActionId
})
this.props.asyncActionWithId({
actionId: asyncActionId,
...whateverParams
})
}
static getDerivedStateFromProps(newProps, prevState) {
if (prevState.asyncActionId) {
const returnedQuery = newProps.queries.find(q => q.id === prevState.asyncActionId)
return {
asyncActionId: get(returnedQuery, 'status', '') === 'PENDING' ? returnedQuery.id : null
}
}
return null;
}
}
With the queries reducer like this:
import get from 'lodash.get'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const FAIL = 'FAIL'
export default (state = [], action) => {
const id = get(action, 'config.actionId')
if (/REQUEST_DATA_(POST|PUT|DELETE|PATCH)_(.*)/.test(action.type)) {
return state.concat({
id,
status: PENDING,
})
} else if (
/(SUCCESS|FAIL)_DATA_(GET|POST|PUT|PATCH)_(.*)/.test(action.type)
) {
return state
.filter(s => s.status !== PENDING) // Delete the finished ones (SUCCESS/FAIL) in the next cycle
.map(
s =>
s.id === id
? {
id,
status: action.type.indexOf(SUCCESS) === 0 ? SUCCESS : FAIL,
}
: s
)
}
return state
}
In the end, my CallbackableComponent knows if the query has finished by checking if this.state.asyncActionId is present.
But this comes at the cost of:
Adding an entry to the store (though this is inevitable)
Adding a lot of complexity on the component.
I would expect:
the asyncActionId logic to be held on the saga side (eg. when the async action is connected using mapActionsToProps, it returns the id when called: const asyncActionId = this.props.asyncAction(params), like setTimeout)
the store part to be abstracted by redux-saga, just like react-router is in charge of adding the current route to the store.
For now, I can't see a cleaner way to achieve this. But I would love to get some insights on this!
Maybe I didn't understand the problem your facing but I guess they meant the client would look something like this:
mapStateToProps = state => {
return {
loading: state.loading,
error : state.error,
data : state.data
};
}
and the component will render like:
return(
{this.props.loading && <span>loading</span>}
{!this.props.loading && <span>{this.props.data}</span>}
{this.props.error && <span>error!</span>}
)
and when requested action is dispatched the its reducer will update the store state to be {loading: true, error: null}.
and when succeeded action is dispatched the its reducer will update the store state to be {loading: false, error: null, data: results}.
and when failed action is dispatched the its reducer will update the store state to be {loading: false, error: theError}.
this way you won't have to use componentWillReceiveProps .
I hope it was clear.

Preventing component updates from multiple props in react

I have a react/redux app which has a recharts chart which animates when data is changed.
I'm using Redux and most of my actions only change a single state property which results in a single props pass. However, some of my actions are now using thunks for some async actions and calling other actions.
For example, I might have an action getChartData which would be called when the user selects an axis.
export let getChartData = axis => dispatch => {
// trimmed for brevity
fetchJSON(url).then(data => {
dispatch(dataRetrievalSuccess(data));
dispatch(updateSelectedAxis(axis));
}).catch(error => {
dispatch(dataRetrievalError(error));
});
};
In this example the updateSelectedAxis value will change a local state property responsible for displaying the currently selected axis and the dataRetrievalSuccess function would be responsible for passing props.data to the chart.
The problem I'm trying to solve is to prevent the chart from updating when the selectedAxis props of the component change but the data hasn't.
I thought I would be able to use something like componentWillRecieveProps but the issue I have here with my above thunk example is that I get one call to componentWillRecieveProps when I call dataRetrievalSuccess which has the same data in both this.props.data and nextProps.data so I can prevent the update. However when I subsequently call updateSelectedAxis I don't have the data as part of the props as it's already changed, so I can't perform logic operations based on the two values.
I thought this was possibly an ordering issue, but even if I pack this into a single action I still get multiple setting of props.
Would I solve this issue by packaging up the data and the change of axis into a single object?
I'm not quite sure the best way to go about this architecturally and would welcome any suggestions.
EDIT:
Just to expand a little, I am dispatching two actions, both which change their own bit of state which causes two renders.
I've tried writing something like this:
shouldComponentUpdate(nextProps, nextState) {
if(this.dataHasChanged(nextProps)) {
return true;
}
return false;
}
Which almost works, but each time the data the chart shows is one render behind where it needs to be.
You can access the current State of store under action creator using thunk (as thunk inject the state for you.) , then compare ajax response data with previous state data to dispatch new action.
export let getChartData = axis => (getState, dispatch) => {
// trimmed for brevity
fetchJSON(url).then(data => {
if(getState().data !== data){
dispatch(dataRetrievalSuccess(data));
dispatch(updateSelectedAxis(axis));
}
}).catch(error => {
dispatch(dataRetrievalError(error));
});
};

Pass values as parameters state from the component or access the status in the action creator?

In my project I have action creator that depend on values ​​that are in the state of the application to generate a new value or to decide what action to dispatch. My question is to know which is the right way to do it. I thought of two ways. Access those values ​​within the action creator:
export const changePreviousPage = () => {
return (dispatch, getState) => {
let pagination = getState().appReducers.availability.pagination;
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
dispatch({
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
});
}
};
The other option I thought was to pass the value from the component to the action creator:
In my component
class Pagination extends Component {
...
handlePreviousPage() {
const {pagination} = this.props;
this.props.changePreviousPage(pagination);
}
...
}
In my action creator
export const changePreviousPage = pagination => {
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
return{
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
}
};
What is the best way to address it ?
In my opinion always use/retrieve the state at the closest time to execution, here the action creator (or rather more specifically the thunk you are returning that would then execute).
Remember that dispatch may have any number of middleware running before the actual store.dispatch call. This can include async middleware, so the state may have changed in between calling the dispatch and the store.dispatch call it will ultimately run.
Another one to consider is you may be dispatching multiple things in an action creator which change the state and invalidate what you passed into the action creator at the top. Also a reason why I consider let state = getState() at the top of an action creator a bad idea unless you are very sure nothing is going to change during your processing (as soon as you involve any API calls I would always use getState() again instead of using a stored variable).
Also putting data from state into props (using a redux container and connect helper method) will cause a rerender every time this changes, which could have a performance impact in some cases.
My personal coding preference is also to keep things as simple as possible in mapDispatchToProps (assuming that is where you're passing in your handlers like handlePreviousPage) and avoid any data processing (in your example it's not much, but you can easily see how that may get out of hand if you're preparing data for your action creator).

React JS Freezes Browser

I have a React component which has 2000 elements and based on some filter conditions I update my state, which internally causes re-rendering. Everything seems to be working fine. But when I togglefilter from 2000 elements to say 1000 elements and back&forth, the rendering takes a lot of time and sometimes the browser freezes. I did chrome timeline profiling, the major time consuming piece is rendering. Any help would be appreciated.
As suggested by #enjoylife is a great step but what if you have many components structures in your view, that would be very difficult to debug even memoising the component won't be able to subside the continuous or loop rendering.
I learnt this after I ran into strange freezing and weird error that wouldn't stop any time a user logged in on the homepage. Imagine of all screens. Sometimes, you would hardly notice your component re-rending.
Detect your screen/page (loop) re-rendering with console log
const Home = () => {
conso.log('home re-rending')
// some hooks
return <BigComponent />
}
As written above. The logs must not show more than a limited time deemed after a component has mounted. In my case, it's once. But if it is too much(logs) and would certainly freeze your pc. Therefore, follow the below steps carefully and retrace your steps.
Tips and prerequisite before trying out this proposed solution. Please make sure you have style guide setup e.g. Eslint, it's great. In my case, I reproduced the source code with cra, then sorted out the first and last listed problem which I encountered.
Be careful with the use of React hooks such as useEffect especially. Avoid causing a side effect in a component.
In my case, I created a reusable useUpdateEffect hook and what I intend it to solve as par the name was to detect an update of React props or window props, but it backfires, I won't share the code.
Also, do extra check if you passed correct and expected dependencies, on this Eslint deserve an accolade.
Avoid random keys in React list. Use unique and constant keys in a component list as react depend on it to identify each item. According to react library
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. You may use the item index as a key as a last resort:
Avoid variable name conflict in your reducer and React component. Please consider the use of style guides as your friend to avoid this fall.
I made the stupid mistake to create a Foo class and use in its render function, which also leads to the freezing scene. Write here for anyone who could meet this problem again.follow this thread.
Avoid infinite loops, Imagine rendering a lot of data at a go. this happen
just in case you share my fate, I urge you to check your loops and make sure you do not have a += instead of -= (or vice versa). Those infinite loops can be quite a big pain in the neck.
Keep your reducer as a reducer, Avoid Action creator, an API call in your reducer or using another reducer in your reducer so, for instance, reducerA in reducerB. When you call to update reducerA in reducerB, the update in reducerA would trigger an update in reducerB whereby cause page/screen to re-render multiple times. for example
// this react reducer in my case
// reducer js file - reducerB
const useBusinesses = () => {
// reducerB as discussed above - the loading context
const { loading } = useLoadingContext(); // the culprit
const [data, setData] = useState(initialState); // initial state,
const [state, dispatch] = useReducer(reducer, data);
useEffect(() => setData(state), [state, setData]);
const { businesses, errorMessage } = state;
const setBusinesses = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_SUCCESS`, data: payload });
const setBusinessesError = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_ERROR`, data: payload });
const fetchBusinesses = async (lglt, type = 'food', limit = 12) => {
try {
// update reducerB: triggers multiple update in reducerA while requesting is pending
loading(FETCH_BUSINESSES, true);
const request = await API.businesses.getWithquery(
`long=${lglt[0]}&latt=${lglt[1]}&limit=${limit}&type=${type}`
);
loading(FETCH_BUSINESSES, false);
setBusinesses(request.data);
} catch (err) {
loading(FETCH_BUSINESSES, false);
// if (!err.response) dispatch(alertMessage(FETCH_BUKKAS, true, 'Please check your network'));
setBusinessesError(err.response.data);
}
});
return { businesses, errorMessage, fetchBusinesses };
};
export const [BusinessesProvider, useBusinessesContext] = constate(useBusinesses);
//home js file
Home = () => {
const { fetchBusinesses } = useBusinessContext();
conso.log('home re-rending')
// some hooks
useEffect(() => {
console.log('am i in trouble, yes!, how many troubles')
fetchBusinesses(coordinates)
}, [fetchBusinesses, coordinates])
return <BigComponent />
}
A quick fix is to implement shouldComponentUpdate See the docs, for whichever child component is being rendered ~2000 times.
shouldComponentUpdate: function(nextProps, nextState) {
return this.props.value !== nextProps.value;
}
Another quick check is to ask yourself if your following the convention of using small, stateless children, passing only props. If not, it might be time to refactor.

Resources