Archived course lesson

Add RecipeListItem component

Part of Frontend Serverless with React and GraphQL

Objective: We will create the recipe card that we will use on the recipe list type of pages.

[Click to download the mock recipes](/downloads/frontend-serverless-mock-recipes.zip)

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.

npm install --save react-ellipsis-text graphcms-image

Now let’s create our component

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:

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>
  );
};