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 (
    <StyledRecipe
    xs={{ 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 (
    <Warning
    warningHeader="No Recipes"
    warningText="No Recipes are present. Why not add one?"
    />
    );
    return (
    <Row>
    {recipesList.map((recipe: Recipe) => (
    <RecipeListItem
    recipe={recipe}
    parentRoute={parentRoute}
    key={`${recipe.id}-${queryType}`}
    />
    ))}
    </Row>
    );
    };