-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvement suggestion around dispatching a fetch action from within a functional component and handling errors #186
Comments
Hey, type Props = {
courses: CourseModel[];
onFetchCourseRequest: () => void // or typeof courseActions.fetchCoursesRequest
};
A short clarification: actions$ epic should never complete because your epic will stop working. So that's why "ignore everything except complete and error" doesn't make sense in this case in which what you really want is your "response" to be pushed to the redux state. EDIT: If there is a particular piece of information that was misleading you in the guide please point me to it so I can fix it. Thanks! |
Hi @piotrwitek, many thanks for responding :) Overall I have found the guide very useful for configuring actions, epics, reducers etc. Also I have found using typesafe-actions, createAsyncAction etc. useful. I have been using the playground example, specifically FCCounter and FCCounter connected (using React.FC...) as an example for how to hook up functional component to actions. Also initially used, the epic from todos. Additionally found the todo's reference implementation useful. I have since modified code and got it working, as listed below. In the end I included mapStateToProps and _ dispatchProps_ in the same source file as the functional component. My particular struggles as a new user so far have been:
Container import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';
import Course from '../components/Course/Course';
import { courseModels } from '../redux/features/course';
import { courseSelectors } from '../redux/features/course';
import { fetchCoursesAsync } from '../redux/features/course/actions';
import { RootState } from 'ReduxTypes';
type ErrorReport = { hasError: boolean; error?: Error };
type StateProps = {
isLoading: boolean;
courses: courseModels.Course[];
error: ErrorReport;
};
/**
* Redux dispatch and state mappings
*/
const dispatchProps = {
fetchCourses: fetchCoursesAsync.request,
};
const mapStateToProps = (state: RootState): StateProps => ({
isLoading: state.courses.isLoadingCourses,
courses: courseSelectors.getReduxCourses(state.courses),
error: courseSelectors.getReduxCoursesError(state.courses),
});
/**
* Component property type definitions
*/
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
/**
* CourseList component
*/
const CourseList = ({
courses = [],
error,
fetchCourses,
isLoading,
}: Propas): JSX.Element => {
// fetch course action on mount
useEffect(() => {
console.log('COURSELIST FETCHING COURSES');
fetchCourses();
}, [fetchCourses]);
if (isLoading) {
return <p>Loading...</p>;
}
if (error && error.hasError && error.error) {
throw error.error;
// if throw an error then encapsulating error boundary catches and displays.
// However when the container is loaded again via clicking on a Navbar link the useEffect
// action does not trigger.
// Alternatively, if the error is rendered inside the container then the useEffect hook is
// still activated if the container is loaded again (e.g. via clicking on a Navbar link).
// return <p>{JSON.stringify(error.error, null, 2)}</p>;
}
return (
<div style={{ marginTop: 20, padding: 30 }}>
{
<Grid container spacing={2 as GridSpacing} justify="center">
{courses.map(element => (
<Grid item key={element.courseID}>
<Course course={element} />
</Grid>
))}
</Grid>
}
</div>
);
};
/**
* Exports
*/
export default connect(
mapStateToProps,
dispatchProps,
)(CourseList); Epic import { Epic } from 'redux-observable';
import { isActionOf } from 'typesafe-actions';
import { of } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';
import { fetchCoursesAsync } from './actions';
import { RootAction, RootState, Services } from 'ReduxTypes';
export const fetchCoursesRequestAction: Epic<
RootAction,
RootAction,
RootState,
Services
> = (action$, state$, { courseServices }) =>
action$.pipe(
filter(isActionOf(fetchCoursesAsync.request)),
switchMap(() =>
courseServices.default.getCourses().pipe(
map(fetchCoursesAsync.success),
catchError((error: Error) =>
of(fetchCoursesAsync.failure({ hasError: true, error: error })),
),
),
),
); |
Thanks for the feedback, I'll think about some ways to improve the guide in the mentioned areas. It can be global by some kind of alerts (invoked from within epic by side effect using tap operator) or locally by just rendering error.message in error section in your view component. |
Thanks @piotrwitek. I am currently having my fetch epic place an error object on the redux state which is then picked up in the client component. However, experiencing problems with There are very few tutorials and resources that I have looked at "so far" that seem to give an end to end example of error handling fetch requests on redux store, i.e. from epic http handling to consuming in client container/component a http success or http failure action. They are probably out there, I just have not searched far enough yet, lol. So, I think that a recommended pattern for error handling would definitely be a very popular topic if it was present in the guide for users that are new to react/redux.... |
@dcs3spp here is an example implementation from my WIP side-project about global error handling: https://github.com/piotrwitek/react-redux-typescript-realworld-app/blob/master/src/features/articles/epics.ts#L46 Unfortunately, I don't have much time to work on each of my side-projects full time so I would need some funding to add missing parts as local error handling implementation there. You can always create a new feature request there to start tracking it and gathering funds. |
Many thanks @piotrwitek. Very helpful. I will bookmark the link. It looks like I am doing the same thing with respect to error handling in the epics, i.e. dispatching an action for request success / error with message or custom payload. My current difficulties have been processing a dispatched error action within a client component. I have included the details and my current thoughts below and might later link or raise on the react-redux-typescript-realworld-app repository.....Apologies, in advance for the lengthy discussion below.... Within a client component, I have used the useEffect hook for fetching data - a fetch action resets any previous associated error state with that fetch action. Once the client component throws an error it is displayed in the Error Boundary's material-ui dialog. When I then revisit the route associated with the client component the useEffect is not being triggered to perform another fetch. I think this is because the client component is already initialised and in an exceptional / failed status. Because the fetch request is not issued:
Hence I resorted to having the client component redirect to an error page instead of throwing the error. This way when I revisit the link associated with the client component the component is reinitialised and useEffect is triggered to issue the fetch request. The fetch request is made, thus clearing the previous error state associated with the request. This is almost working, but 'fails' for the following sequence of events:
Alternatively, instead of displaying a dialog or being redirected to an error page, the client could have a dedicated renderer associated with error state. For example, if an error occurs the component does not display a data table but instead displays a formatted error text paragraph..... |
It seems you can have a problem of not unmounting the component when an error occurs, that's why it doesn't run request to fetch the data and clear error on the mount. Not directly related: You're using Key points:
|
Thanks @piotrwitek will investigate suggestions 1 and 3. Your help and suggestions are appreciated :) Yes, the reducer for fetch request:start resets error state. Will have an experiment on how to unmount functional component when error is explicitly thrown.... Many thanks again :) |
Hi,
Firstly, I have found the patterns in the guide very helpful for configuring actions, epics, reducers etc. However, I do not understand how to dispatch a fetch API request action and subsequently bind to a fetch successful action. I have managed to create an epic for a fetch API request that then triggers a success or error action depending upon the success/failure of the API request.
I do not understand how to hook this up to a functional component, as highlighted in the comments of the CourseList functional component code listing below:
CourseList Functional Component
CourseListConnected
Epic for Fetching a Course from API
The text was updated successfully, but these errors were encountered: