Frontend Serverless with React and GraphQL

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


15. Add loading, error, warning alerts

Objective: We will create loading, error, and warning components for use across our app.

We could see from our last commit that fetching data requires preparing for the worst and we had to add lines for both loading and error conditions. We solved that by adding paragraph tags and text, but this would really be nicer if we had a loading and error component that we could just use for all of these situations where we are loading data from our server.

components/notify/Error.tsx

import { Row, Col, Alert } from 'antd';
import styled from 'styled-components';
const StyledCol = styled(Col)`
text-align: center;
margin-top: 200px;
`;
export const Error = ({ errorText }: { errorText: string }) => (
<Row>
<StyledCol span={12} offset={6}>
<Alert
message={'An error has occured.'}
description={errorText || 'Error'}
type="error"
/>
</StyledCol>
</Row>
);

We are using the Ant Design <Alert> component here that we stylize to make sure that it gets centered on the screen. The component also allows us to pass in text that it will display in the alert box. Next we can add a loading component.

components/notify/Loading.tsx

import { Spin } from 'antd';
import styled from 'styled-components';
const StyledSpinner = styled.div`
text-align: center;
border-radius: 4px;
margin-bottom: 20px;
padding: 30px 50px;
margin: 200px 0;
`;
export const Loading = () => (
<StyledSpinner>
<Spin size="large" />
</StyledSpinner>
);

Here we are using a spinning component but the styling is similar to the Error component. Now, let's create a <Warning> component. This will accept a warning text and display it like the <Error> component.

components/notify/Warning.tsx

import { Row, Col, Alert } from 'antd';
import styled from 'styled-components';
const StyledCol = styled(Col)`
text-align: center;
margin-top: 200px;
`;
export const Warning = ({
warningHeader,
warningText,
}: {
warningHeader: string;
warningText: string;
}) => (
<Row>
<StyledCol span={12} offset={6}>
<Alert
message={warningHeader || 'Warning'}
description={warningText || 'An unknown warning has occured.'}
type="warning"
/>
</StyledCol>
</Row>
);

We can now use each of these components in the <RecipesList> component that we just created. In this case, the warning will be used to tell the user when there aren't any items in our list. This is the updated list:

components/RecipesList.tsx

import { QueryHookOptions, useQuery } from '@apollo/react-hooks';
import { recipesGraphQL } from '../graphql/queries/recipes';
import { userLikesGraphQL } from '../graphql/queries/userLikes';
import * as _ from 'lodash';
import { Row } from 'antd';
import { Recipe } from '../generated/apollo-components';
import { Loading } from './notify/Loading';
import { Warning } from './notify/Warning';
import { Error } from './notify/Error';
export enum queryEnum {
userLikes = 'userLikes',
recipes = 'recipes',
}
type RecipesListProps = {
options?: QueryHookOptions;
parentRoute: string;
queryType: queryEnum;
};
export const RecipesList = ({
options,
parentRoute,
queryType,
}: RecipesListProps) => {
const query =
queryType === queryEnum.recipes ? recipesGraphQL : userLikesGraphQL;
const { loading, data, error } = useQuery(query, options);
const parentArray = _.get(data, queryType);
const recipesList = _.map(parentArray, (value) =>
_.get(value, 'recipe', value),
);
if (loading) return <Loading />;
if (error) return <Error errorText={`${error}`} />;
if (recipesList.length === 0)
return (
<Warning
warningHeader="No Recipes"
warningText="No Recipes are present. Why not add one?"
/>
);
return (
<Row>
{recipesList.map((recipe: Recipe) => (
<p key={recipe.id}>{recipe.title}</p>
))}
</Row>
);
};

We are now loading all three of those components and using them in if blocks right before the final return statement.