DeenruvDeenruv
Rozszerzanie panelu administracyjnego

Klient GraphQL

Integracja GraphQL w @deenruv/react-ui-devkit — apiClient, hooki i wzorzec selektorów

@deenruv/react-ui-devkit udostępnia klienta GraphQL zbudowanego na Zeus Thunder. Obsługuje standardowe zapytania, mutacje, przesyłanie plików i automatyczne wstrzykiwanie customFields do wszystkich zapytań.

apiClient

Główny klient GraphQL do zapytań i mutacji. Wykorzystuje składnię Zeus Thunder dla operacji bezpiecznych typowo.

Zapytania

import { apiClient } from '@deenruv/react-ui-devkit';

// Proste zapytanie
const result = await apiClient('query')({
  product: [
    { id: 'product-123' },
    {
      id: true,
      name: true,
      slug: true,
      description: true,
      enabled: true,
      featuredAsset: {
        id: true,
        preview: true,
      },
      // customFields są automatycznie wstrzykiwane!
    },
  ],
});

console.log(result.product.name);

Mutacje

import { apiClient } from '@deenruv/react-ui-devkit';

const updated = await apiClient('mutation')({
  updateProduct: [
    {
      input: {
        id: 'product-123',
        enabled: true,
        translations: [
          {
            languageCode: LanguageCode.en,
            name: 'Updated Product',
            slug: 'updated-product',
            description: 'New description',
          },
        ],
      },
    },
    {
      id: true,
      name: true,
      slug: true,
    },
  ],
});

Zapytania listowe z paginacją

const result = await apiClient('query')({
  products: [
    {
      options: {
        take: 25,
        skip: 0,
        sort: { name: SortOrder.ASC },
        filter: { name: { contains: 'hoodie' } },
      },
    },
    {
      totalItems: true,
      items: {
        id: true,
        name: true,
        slug: true,
        enabled: true,
      },
    },
  ],
});

console.log(result.products.totalItems);
console.log(result.products.items);

Pola niestandardowe są wstrzykiwane automatycznie. apiClient automatycznie dodaje selekcję customFields do wszystkich zapytań poprzez manipulację AST GraphQL. Nie musisz ich ręcznie żądać.

apiUploadClient

Specjalizowany klient do mutacji przesyłania plików przy użyciu danych formularza multipart:

import { apiUploadClient } from '@deenruv/react-ui-devkit';

const result = await apiUploadClient('mutation')({
  createAssets: [
    {
      input: [
        { file: myFile },     // Obiekt File z input/drag-drop
      ],
    },
    {
      '...on Asset': {
        id: true,
        name: true,
        source: true,
        preview: true,
      },
      '...on MimeTypeError': {
        errorCode: true,
        message: true,
      },
    },
  ],
});

Hooki React

useQuery

Deklaratywne zapytania GraphQL, które wykonują się automatycznie po zamontowaniu i ponownie po zmianie zależności:

import { useQuery } from '@deenruv/react-ui-devkit';

function ProductDetail({ productId }: { productId: string }) {
  const { data, loading, error, runQuery } = useQuery(
    (vars) =>
      apiClient('query')({
        product: [
          { id: vars.id },
          {
            id: true,
            name: true,
            slug: true,
            description: true,
          },
        ],
      }),
    {
      initialVariables: { id: productId },
      onSuccess: (data) => {
        // Wypełnij formularz pobranymi danymi
        setField('name', data.product.name);
        setField('slug', data.product.slug);
      },
      stopRefetchOnChannelChange: false,
    },
  );

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage message={error} />;

  return <div>{data?.product.name}</div>;
}

Opcje

OpcjaTypOpis
initialVariablesobjectZmienne przekazywane do zapytania przy pierwszym wykonaniu
onSuccess(data) => voidCallback wywoływany po pomyślnym zakończeniu zapytania
stopRefetchOnChannelChangebooleanJeśli true, nie odświeżaj zapytania przy zmianie aktywnego kanału (domyślnie: false)

Wartość zwracana

WłaściwośćTypOpis
dataT | undefinedDane wynikowe zapytania
loadingbooleanCzy zapytanie jest w trakcie wykonywania
errorstring | undefinedKomunikat błędu jeśli zapytanie nie powiodło się
runQuery(vars?) => Promise<T>Ręczne ponowne wykonanie zapytania z opcjonalnymi nowymi zmiennymi

useLazyQuery

Jak useQuery, ale nie wykonuje się automatycznie. Zapytanie uruchamia się dopiero po wywołaniu zwróconej funkcji:

import { useLazyQuery } from '@deenruv/react-ui-devkit';

function SearchComponent() {
  const { data, loading, runQuery } = useLazyQuery((vars) =>
    apiClient('query')({
      search: [
        { input: { term: vars.term, take: 10 } },
        {
          totalItems: true,
          items: { productId: true, productName: true },
        },
      ],
    }),
  );

  const handleSearch = (term: string) => {
    runQuery({ term });
  };

  return (
    <div>
      <SearchInput onChange={handleSearch} />
      {loading && <Spinner />}
      {data?.search.items.map((item) => (
        <div key={item.productId}>{item.productName}</div>
      ))}
    </div>
  );
}

useMutation

Hook React do mutacji GraphQL:

import { useMutation } from '@deenruv/react-ui-devkit';

function DeleteButton({ productId }: { productId: string }) {
  const { loading, runMutation } = useMutation((vars) =>
    apiClient('mutation')({
      deleteProduct: [
        { id: vars.id },
        { result: true },
      ],
    }),
  );

  return (
    <Button
      disabled={loading}
      onClick={() => runMutation({ id: productId })}
    >
      Delete
    </Button>
  );
}

Wzorzec selektorów

Dla złożonych lub wielokrotnie używanych selekcji zapytań, wyodrębnij je do obiektów selektorów:

graphql/selectors.ts
export const productSelector = {
  id: true,
  name: true,
  slug: true,
  description: true,
  enabled: true,
  featuredAsset: {
    id: true,
    preview: true,
  },
  variants: {
    id: true,
    name: true,
    sku: true,
    priceWithTax: true,
    stockOnHand: true,
  },
} as const;

export const productListSelector = {
  totalItems: true,
  items: {
    id: true,
    name: true,
    slug: true,
    enabled: true,
    featuredAsset: { preview: true },
  },
} as const;
graphql/queries.ts
import { apiClient } from '@deenruv/react-ui-devkit';
import { productSelector, productListSelector } from './selectors';

export const getProduct = (id: string) =>
  apiClient('query')({
    product: [{ id }, productSelector],
  });

export const getProducts = (options: ListQueryOptions) =>
  apiClient('query')({
    products: [{ options }, productListSelector],
  });
graphql/mutations.ts
import { apiClient } from '@deenruv/react-ui-devkit';
import { productSelector } from './selectors';

export const updateProduct = (input: UpdateProductInput) =>
  apiClient('mutation')({
    updateProduct: [{ input }, productSelector],
  });

Ten wzorzec utrzymuje Twoje selekcje GraphQL zgodnie z zasadą DRY i ułatwia zapewnienie spójnych kształtów danych w zapytaniach i mutacjach.

deenruvAPICall

Niskopoziomowa funkcja wywołań API używana wewnętrznie przez apiClient i apiUploadClient. Obsługuje:

  • Uwierzytelnianie — Wstrzykuje token Bearer ze store ustawień
  • Token kanału — Wstrzykuje token aktywnego kanału
  • Kod języka — Przekazuje aktualny kod języka jako parametr
  • Wstrzykiwanie pól niestandardowych — Manipuluje AST GraphQL, aby dodać selekcję customFields
  • Obsługa błędów — Parsuje i normalizuje błędy GraphQL

Zazwyczaj nie musisz używać deenruvAPICall bezpośrednio. Zamiast tego użyj apiClient lub apiUploadClient, które zapewniają wyższopoziomowe API bezpieczne typowo.

Organizacja kodu GraphQL

Stosuj tę konwencję dla kodu GraphQL w pluginach:

plugin-ui/
  graphql/
    index.ts           # Re-eksporty
    queries.ts         # Funkcje zapytań
    mutations.ts       # Funkcje mutacji
    selectors.ts       # Wielokrotnego użytku obiekty selekcji
    scalars.ts         # Definicje niestandardowych skalarów (jeśli potrzebne)

Taki podział separuje zagadnienia GraphQL od kodu komponentów i ułatwia znajdowanie i ponowne używanie zapytań.

Na tej stronie