Введение в Apollo GraphQL с помощью React Hooks и Context API

Ранее я писал о том, как использовать Angular с Apollo Graph QL здесь. Я смотрел видео о GraphQL на сайте egghead.io от Eve Porcello и подумал, что было бы интересно имитировать действия, которые она выполняла на игровой площадке GraphQL (https://pet-library.moonhighway.com ) С использованием Angular и Apollo GraphQL Client.

Поскольку я давно хотел попробовать React, я подумал о том, чтобы переписать приложение angular в React, используя хуки и функциональные компоненты, и хотел бы поделиться с вами своим опытом обучения.

Проект

Наш проект будет простым клоном игровой площадки с библиотекой домашних животных с нуля с использованием React и Apollo Client, с функциями, включая аутентификацию, возврат списка всех домашних животных и домашних животных, регистрируемых / выписываемых. Мы не будем использовать компоненты на основе классов в этом проекте, поскольку перехватчики реакции позволяют нам управлять состоянием локального компонента и жизненным циклом компонента.

Вы можете просмотреть полный исходный код здесь.

Структура папки

На изображении выше показана структура папок, которую мы будем использовать. Все повторно используемые компоненты будут помещены в папку компонентов, страницы, которые будут представлять страницу или маршрут, будут помещены в папку страниц. Хуки, которые используются компонентами, попадают в папку хуков. Контексты React, к которым будут обращаться компоненты, будут помещены в папку провайдеров.

Папка Components будет включать в себя следующие компоненты:

  • List.js: простой презентационный компонент, который отображает список домашних животных и принимает домашних животных в качестве реквизита.
  • CheckIn.js: компонент для регистрации питомца, а также для обновления списка.
  • CheckOut.js: компонент для проверки питомца, компонент CheckIn и CheckOut принимает идентификатор питомца в качестве опоры.
  • SelectStatus.js: компонент для фильтрации домашних животных по статусу.

Папка Pages будет включать в себя следующие компоненты:

  • ListContainer.js: компонент контейнера, который будет содержать как раскрывающийся список фильтра, так и компонент списка.
  • Login.js: компонент, который занимается аутентификацией.

Давайте воспользуемся приложением create-react-app для создания нашего проекта.

create-react-app react-apollo-graphql-hooks

В нашем проекте мы также будем использовать реактивную загрузку, поэтому давайте добавим ее в наш проект, набрав:

npm install react-bootstrap bootstrap

Затем мы создадим SelectStatus.js в папке компонентов и добавим следующий код.

export const SelectStatus = ({ petStatus, defaultValue, onSelect }) => {
  const setSelect = (e) => {
    e.preventDefault();
    let index = e.target.options.selectedIndex;
    let status = petStatus[index];
    if (onSelect) {
      onSelect(status);
    }
  };
return (
    <>
      <Form.Group controlId="status">
        <Col>
          <Form.Label>Pet Status:</Form.Label>
        </Col>
        <Col>
          <Form.Control
            as="select"
            defaultValue={defaultValue?.name}
            onChange={(e) => setSelect(e)}
          >
            {petStatus.map((item) => {
              return <option key={item.key}>{item.name}</option>;
            })}
          </Form.Control>
        </Col>
      </Form.Group>
    </>
  );
};

Пока не обращайте внимания на свойства {petStatus, defaultValue, onSelect}, мы вернемся к ним позже. Как видите, этот компонент - всего лишь презентационный компонент, который не хранит никакого внутреннего состояния и просто отображает компонент начальной загрузки «select», просматривая список статусов питомца.

Перейдем к компоненту списка.

export const List = ({ pets }) => {
  return (
    <>
      <div className="row mt-4">
        <div className="col-sm-8">
          <table className="table table-striped">
            <thead>
              <tr>
                <td className="w-25">
                  <p> Pet </p>
                </td>
                <td className="w-30">
                  <p> Category</p>
                </td>
                <td className="w-50">
                  <p> Customer</p>
                </td>
                <td className="w-50">
                  <p> Action</p>
                </td>
              </tr>
            </thead>
            <tbody>
              {pets.map((item) => {
                return (
                  <tr key={item.id}>
                    <td>{item.name}</td>
                    <td>{item.category}</td>
                    <td>{item.inCareOf?.name}</td>
                    <td>
                      {item.status === "AVAILABLE" ? (
                        <CheckOut petId={item.id} />
                      ) : (
                        <CheckIn petId={item.id} />
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
      ;
    </>
  );
};

Это также просто презентационный компонент, который отображает список домашних животных. Он также имеет компонент CheckIn / CheckOut, который принимает идентификатор питомца в качестве опоры. Вернемся к компонентам CheckIn / CheckOut позже.

Прежде чем создавать компоненты контейнера, давайте напишем нашу первую ловушку Apollo Client. Создайте usePetsHooks.js в папке хуков с помощью следующего кода.

import { useQuery } from "@apollo/client";
import gql from "graphql-tag";
const petFieldsQuery = gql`
  fragment petFields on Pet {
    id
    name
    category
    status
    inCareOf {
      name
    }
  }
`;
export const filterPetsQuery = gql`
  query petsQuery($status: PetStatus) {
    allPets(status: $status) {
      ...petFields
    }
  }
  ${petFieldsQuery}
`;
export default (status) => {
  return useQuery(filterPetsQuery, {
    fetchPolicy: "network-only",
    variables: {
      status: status,
    },
  });
};

Мы используем хук useQuery Apollo Client для получения данных GraphQL. Вызов useQuery возвращает объект со свойствами, включая загрузку, ошибку, данные и функцию повторной выборки. Мы рассмотрим, как использовать функцию refetch, когда перейдем к функциям CheckIn / CheckOut. Я также сохраняю fetchPolicy как «только для сети», поскольку мы не заинтересованы в кэшировании результатов запросов в этом проекте.

Нам также необходимо убедиться, что список обновляется, когда мы выполняем CheckIn / CheckOut, чтобы текущий питомец исчез из списка. Помните, что useQuery возвращает функцию повторной выборки? И мы хотим вызвать эту функцию refetch из компонента CheckIn, когда мы регистрируем питомца. Как это сделать, не делая компонент List сквозным компонентом для повторной выборки?

Один из способов - использовать Context API, поэтому нам не нужно вручную передавать реквизиты через компонент List в компоненты CheckIn / CheckOut. Итак, используя API, мы можем создать новый контекст. Создайте файл refetchProvider.js со следующим кодом.

import React from "react";
export const PetsContext = React.createContext({});
export const PetsProvider = PetsContext.Provider;

Провайдер может содержать любые значения, а также может быть функцией (действием). В следующем разделе мы установим функцию refetch в качестве значения поставщика.

Затем давайте создадим компонент-контейнер ListContainer.js.

export const ListContainer = () => {
const petStatus = [
    { key: 1, id: null, name: "All" },
    { key: 2, id: "AVAILABLE", name: "Available" },
    { key: 3, id: "CHECKEDOUT", name: "Checked Out" },
  ];
const [selectedStatus, setSelectedStatus] = useState(() => null);
const { loading, error, data, refetch } = usePetsQuery(
    selectedStatus ? selectedStatus.id : null
  );
const onSelectStatus = (status) => {
    setSelectedStatus(status);
  };
const onRefetch = () => {
    refetch();
  };
if (loading) return "Loading...";
if (error) return `Error! ${error.message}`;
return (
    <>
      <Container className="mt-4">
        <Form>
          <Form.Row>
            <SelectStatus
              petStatus={petStatus}
              onSelect={onSelectStatus}
              defaultValue={selectedStatus}
            />
            <div className="ml-auto">
              <Logout />
            </div>
          </Form.Row>
        </Form>
        <PetsProvider value={() => onRefetch()}>
          <List pets={data.allPets} />
        </PetsProvider>
      </Container>
    </>
  );
};

Мы используем шаблон «Контейнер», чтобы отделить состояние и события от компонентов представления.

const [selectedStatus, setSelectedStatus] = useState(() => null);

Здесь мы используем React.useState для поддержания состояния раскрывающегося списка выбора. useState возвращает массив, и мы можем использовать синтаксис деструктуризации ES6 для доступа к значениям. Когда мы меняем фильтр выбора, нам нужно повторно визуализировать весь компонент списка, и функция обновления (setSelectedStatus) позаботится об этом.

Также обратите внимание, как мы обернули компонент List в PetsProvider. Это помогает нам использовать контекст в каждом компоненте. Мы скоро увидим это в компоненте CheckIn.

Для функциональности регистрации давайте создадим компонент CheckIn.

export const CheckIn = ({ petId }) => {
const refetch = useContext(PetsContext);
const doCheckIn = useCheckInMutation();
  const checkIn = () => {
    doCheckIn(
      {
        variables: { petId: petId },
      },
      { refetchQueries: [`petsQuery`] }
    )
      .then((_) => {
        refetch();
      })
      .catch((e) => console.log(e));
  };
if (!isLoggedIn()) {
    return null;
  }
return (
    <>
      <button onClick={() => checkIn()} className="btn btn-link">
        Check In
      </button>
    </>
  );
};

Мы получаем ссылку на обработчик refetch из useContext API. После того, как произойдет проверка мутации, мы вызовем функцию refetch (), которая, в свою очередь, вызовет обработчик onRefetch в ListContainer.js.

Вывод

Это была моя попытка поделиться тем, что я узнал с помощью перехватчиков React и Context API. В этом примере проекта показано, как поддерживать локальное состояние с помощью useState и как передавать контекст внутренним компонентам, если они находятся где-то в одном дереве компонентов. Более подробную информацию о хуках можно найти здесь.

Вы можете просмотреть полный исходный код здесь.

Примечание из JavaScript In Plain English

Мы запустили три новых издания! Проявите любовь к нашим новым публикациям, подписавшись на них: AI на простом английском, UX на простом английском, Python на простом английском - спасибо и продолжайте учиться!

Мы также всегда заинтересованы в продвижении качественного контента. Если у вас есть статья, которую вы хотели бы отправить в какую-либо из наших публикаций, отправьте нам электронное письмо по адресу [email protected] с вашим именем пользователя Medium, и мы добавим вас в качестве автора. Также сообщите нам, к каким публикациям вы хотите быть добавлены.