Search
Write a publication
Pull to refresh

Apollo Client + Hasura

Введение

Технология GraphQL, в отличие от стандартной REST API, позволяет делать запросы на сервер на по множеству ендпоинтов, а по одному и для получения и изменения данных используются query и mutations. Попробуем создать простое приложение для демонстрации как это работает.

Cоздаем проект

yarn create react-app react-apollo-hasura --template typescript

Пока создается проект заходим на сайт hasura и создаем новый проект.

Создаем простую таблицу с телефонами. Добавляем поля id, name, image, description и price.

Переходим в закладку api.

Там есть ссылка на нашу апишку и хедеры, которые необходимы для доступа к ней.

Добавляем apollo client в проект

yarn add @apollo/client

Создадим файл createApolloClient.ts куда вписыаем

import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
} from "@apollo/client";

export const createApolloClient = () => {
  return new ApolloClient({
    link: new HttpLink({
      uri: "https://living-osprey-95.hasura.app/v1/graphql",
      headers: {
        'content-type': 'application/json',
        'x-hasura-admin-secret': `************`,
      },
    }),
    cache: new InMemoryCache(),
  });
};

туда мы пишем нашу ссылку на апишку и хеддеры.

Для UI я использовал charka.io.

Создаем папку Components где добавим файл types.ts

export type GoodType = {
  id: number
  name: string
  price: number
  image: string
  description: string | null
}

export type GoodTypeFromQuery = {
  id?: number
  name?: string
  price?: number
  image?: string
  description?: string | null 
}

А также файл queries.ts

import { gql } from "@apollo/client";
import { GoodType } from "./types";

export const getGoodsQuery = (fields: Array<keyof GoodType>) => {
  return gql`
    query {
      goods {
        ${fields.join(',\n')}
      }
    }
  `
}

В файле App.ts добавим ApolloProvider а также стейт ключей, которые мы сможем изменять, чтобы продемонстрировать работу GraphQL

import React, { useCallback, useState } from "react";
import { createApolloClient } from "./createApolloClient";
import { ApolloProvider } from "@apollo/client";
import { Box, Heading, Stack } from "@chakra-ui/react";
import Goods from "./Components/Goods";
import { GoodType } from "./Components/types";
import Control from "./Components/Control";

function App() {
  const [client] = useState(createApolloClient());

  const keys: Array<keyof GoodType> = [
    "id",
    "name",
    "price",
    "image",
    "description",
  ];

  const [fields, setFields] = useState<Array<keyof GoodType>>(keys);

  const setField = useCallback(
    ({ key, active }: { key: keyof GoodType; active: boolean }) => {
      if (active) {
        if (!fields.includes(key)) {
          setFields([...fields, key]);
        }
      } else {
        setFields(fields.filter((el) => el !== key));
      }
    },
    [fields, setFields]
  );

  return (
    <ApolloProvider client={client}>
      <Box m={[5, 5]}>
        <Heading>HASURA GRAPHQL EXAMPLE</Heading>
      </Box>
      <Box m={[20, 5]}>
        {keys.map((el, idx) => (
          <Control
            key={idx}
            _key={el}
            isActive={fields.includes(el)}
            setField={setField}
          />
        ))}
      </Box>
      <Stack direction="column">
        <Goods fields={fields} />
      </Stack>
    </ApolloProvider>
  );
}

export default App;

В компоненте Goods.tsx мы будем получать данные товаров в зависимости от активных ключей, которые передаются в компонент

import { useQuery } from "@apollo/client";
import { getGoodsQuery } from "./queries";
import { CircularProgress, Stack, Text, StackDivider } from "@chakra-ui/react";
import { GoodType, GoodTypeFromQuery } from "./types";
import Good from "./Good";
import { Fragment, useEffect } from "react";

interface Props {
  fields: Array<keyof GoodType>;
}

export default function Goods({ fields }: Props) {
  const { loading, error, data, refetch } = useQuery<{ goods : GoodTypeFromQuery[]}>(
    getGoodsQuery(fields)
  );

  useEffect(() => {
    refetch()
  }, [fields])

  console.log(fields);

  return (
    <Stack
      direction="column"
      m={[10, 10]}
      divider={<StackDivider borderColor="gray.300" />}
    >
      {loading && <CircularProgress isIndeterminate color="green.300" />}
      {error && (
        <Text fontSize={"40px"} color="tomato">
          {error.message}
        </Text>
      )}

      {data && (
        <Fragment>
          {data.goods.map((el) => (
            <Good key={el.id} data={el} />
          ))}
        </Fragment>
      )}
    </Stack>
  );
}

Компонент одного товара Good.tsx

import { Center, Image, Text, VStack } from "@chakra-ui/react";
import { getPriceString } from "../helpers/getPriceString";
import { GoodTypeFromQuery } from "./types";

export default function Good({ data }: { data: GoodTypeFromQuery }) {
  return (
    <Center border={"1px solid gray"} marginBottom={10} p={[10, 10]}>
      {data.name && <Text fontWeight={700} marginRight={10}>
        {data.name}
      </Text>}
      {data.image && <Image src={data.image} w="50px" h="50px" m={[5, 5]}/>}
      {data.price && <Text m={[5, 5]}>{getPriceString(data.price)}</Text>}
      {data.description && <Text>{data.description}</Text>}
    </Center>
  );
}

Control.tsx

import { Checkbox } from "@chakra-ui/react";
import { GoodType } from "./types";

export default function Control({
  setField,
  _key,
  isActive,
}: {
  setField: (data: { key: keyof GoodType; active: boolean }) => void;
  isActive: boolean;
  _key: keyof GoodType;
}) {

  return (
    <Checkbox
      colorScheme={"green"}
      checked={isActive}
      defaultChecked
      onChange={() => {
        setField({ key:_key, active: !isActive });
      }}
      m={[2, 2]}
    >
      {_key}
    </Checkbox>
  );
}

Весь фокус в том, что изначально мы получаем все поля товаров.

Вся штука в том, что если мы уберем галочки с некоторых полей, то данные мы получим уже без них.

Вот и вся наука. GraphQL делает работу с апишкой приятной и удобной, а apollo/client под капотом делает все грязную работу за нас.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.