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.
16. Add RecipeListItem component
Objective: We will create the recipe card that we will use on the recipe list type of pages.
Now that we have the <RecipeList>
working, it is time to create the <RecipeListItem>
component. First, let's add two packages that we will need. The first is graphcms-image
and this will handle asynchronously loading the images from GraphCMS and displaying them in a progressive manner. That means we will first see a blurry image, which then will progressively get filled in as the rest of the image gets downloaded. Next we will install the react-ellipsis-text
component which will allow us to ensure that if a title or description is particuarly long that we can cut it off with a certain point and add an ellipsis so that it won't wreck the flow of our cards.
bash
npm install --save react-ellipsis-text graphcms-image
Now let's create our component
components/RecipeListItem.tsx
import { Recipe } from '../generated/apollo-components';import { Col } from 'antd';import styled from 'styled-components';import EllipsisText from 'react-ellipsis-text';import GraphImage from 'graphcms-image';import Link from 'next/link';const StyledRecipe = styled(Col)`${({ theme }) => `padding: 0px ${theme['padding-small']};.card {cursor: pointer;margin-bottom: ${theme['margin-small']};height: 340px;border-radius: 8px;box-shadow: 0 0 16px ${theme['border-color']};border: ${theme['border-width']} solid ${theme['border-color']};}.graphcms-image-outer-wrapper{border: 0px;.graphcms-image-wrapper {border: 0px;position: relative;float: left;width: 100%;height: 200px;background-position: 50% 50%;background-repeat: no-repeat;background-size: cover;img {text-align: center;border-radius: 6px 6px 0px 6px;}}}p {padding: 0px ${theme['padding-small']};}h3 {padding: 0px ${theme['padding-small']};line-height: 1.5em;}`}`;export const RecipeListItem = ({recipe,parentRoute,}: {recipe: Recipe;parentRoute: string;}) => {const { title, description, image, id } = recipe;return (<StyledRecipexs={{ span: 24 }}sm={{ span: 12 }}lg={{ span: 8 }}xl={{ span: 6 }}><div className="card"><Link href={`/${parentRoute}/${id}`}><div>{image ? <GraphImage image={image} /> : null}</div></Link><h3>{title}</h3><p><EllipsisText text={description} length={110} /></p></div></StyledRecipe>);};
Now, lets update our <RecipeList>
component to add our fancy new list item component to it. The final file will look like the following:
components/RecipeList.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';import { RecipeListItem } from './RecipeListItem';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 (<WarningwarningHeader="No Recipes"warningText="No Recipes are present. Why not add one?"/>);return (<Row>{recipesList.map((recipe: Recipe) => (<RecipeListItemrecipe={recipe}parentRoute={parentRoute}key={`${recipe.id}-${queryType}`}/>))}</Row>);};