Graphql

This page demonstrates the steps to creating a client GraphQL using Apollo. For a full example scope, see learnbit-react.graphql

Create a GraphQL provider

Run the following to create a component that will later implement the apollo provider:

$bit
Copiedcopy

Set the following dependencies as peer dependencies, in you env's env.jsonc file:

/* @filename: env.jsonc */
{
  "policy": {
    "peers": [
      {
        "name": "graphql",
        "version": "16.6.0",
        "supportedRange": "^16.6.0"
      },
      {
        "name": "@apollo/client",
        "version": "3.7.5",
        "supportedRange": "^3.7.5"
      }
    ]
  }
}
CopiedCopy

Run the following to install the dependencies:

$bit
Copiedcopy

Implement a class that provides the provider and a client instance:

import type { ReactNode } from 'react';
import {
  ApolloClient,
  ApolloProvider as ApolloProviderLib,
  ApolloClientOptions,
  InMemoryCache,
} from '@apollo/client';

export type ApolloProviderProps = {
  clientConfig?: Partial<ApolloClientOptions<any>>;
  children: ReactNode;
};

export const ApolloProvider = ({
  clientConfig,
  children,
}: ApolloProviderProps) => {
  const client = new ApolloClient({
    uri: clientConfig?.uri || 'https://rickandmortyapi.com/graphql',
    cache: clientConfig?.cache || new InMemoryCache(),
    ...clientConfig,
  });
  return <ApolloProviderLib client={client}>{children}</ApolloProviderLib>;
};
CopiedCopy

See an example component.

As a rule of thumb, context provider components should be configured as peer dependencies, to avoid multiple instances of the same provider in the same app or preview.

/* @filename: env.jsonc */
{
  "policy": {
    "peers": [
      {
        "name": "@learnbit-react/graphql.context.apollo-provider",
        "version": "0.0.1",
        "supportedRange": "^0.0.1"
      }
    ]
  }
}
CopiedCopy
Avoid cyclic dependencies

Setting a component as a peer dependency of itself, or as a regular dependency of its env, will cause a cyclic dependency error. Use a different env for your GraphQL provider!

Create a custom hook

Run the following to create a component that will later implement the custom hook that fetches data from graphql:

$bit
Copiedcopy

Implement a custom hook:

import { gql, useQuery } from '@apollo/client';
/* this component will be created in the next step */
import { Character } from '@learnbit-react/graphql.entities.character';

const GET_CHARACTERS = gql`
  query {
    characters(filter: { gender: "Genderless" }) {
      results {
        name
        gender
      }
    }
  }
`;

export function useCharacters(): Character[] | undefined {
  const { data } = useQuery(GET_CHARACTERS, {});
  const characters = data?.characters.results.map((plainCharacter) =>
    Character.fromObject(plainCharacter)
  );
  return characters;
}
CopiedCopy

See an example component.

Create an entity component

Create an entity component to define the structure of data being fetched, and provide the methods required for the manipulation of that data. This component serves a a source-of-truth for any component handling that type of data, client-side and server-side.

export type CharacterType = {
  name: string;
  gender: 'Male' | 'Female' | 'Genderless' | 'unknown';
  [x: string]: any;
};

export class Character {
  constructor(
    readonly name: CharacterType['name'],
    readonly gender: CharacterType['gender']
  ) {}

  static fromJson(jsonStr: string) {
    const object = JSON.parse(jsonStr);
    return new Character(object.name, object.gender);
  }

  static fromObject(plainCharacter: CharacterType) {
    return new Character(plainCharacter.name, plainCharacter.gender);
  }
}
CopiedCopy

See an example component.

Test

Head over to your use-characters.spec.tsx file, to create an automated test:

import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';

/* import your custom apollo provider */
import { ApolloProvider } from '@learnbit-react/graphql.context.apollo-provider';
import { useCharacters } from './use-characters';

describe('useCharacters', () => {
  it('retrieves rick and morty characters', async () => {
    const wrapper = ({ children }) => (
      <ApolloProvider>{children}</ApolloProvider>
    );

    const { result, waitForNextUpdate } = renderHook(() => useCharacters(), {
      wrapper,
    });

    expect(result.current).toBeUndefined();

    await act(async () => {
      await waitForNextUpdate({ timeout: 5000 });
    });

    expect(result.current).toBeDefined();
    expect(result.current?.length).toBeGreaterThan(0);
  });
});
CopiedCopy

See the demo component for a full example that uses a mock provider.

Run the following to run your tests:

$bit
Copiedcopy

Use the custom hook as a dependency

Create a new component:

$bit
Copiedcopy

Implement the component. Use the previously created hook to implement data fetching and state management:

import { useCharacters } from '@learnbit-react/graphql.hooks.use-characters';

export type CharactersListProps = {
  className?: string;
} & React.HTMLAttributes<HTMLUListElement>;

export function CharactersList({ className = '', ...rest }) {
  const characters = useCharacters();

  return (
    <ul className={className} {...rest}>
      {characters?.map((result, i) => (
        <li key={i}>{result.name}</li>
      ))}
    </ul>
  );
}
CopiedCopy

Create a composition in your characters-list.compositions.tsx file, to preview your component. Use the previously created provider, to provide the necessary context for it:

import { ApolloProvider } from '@learnbit-react/graphql.context.apollo-provider';
import { CharactersList } from './characters-list';

export const BasicCharactersList = () => {
  return (
    <ApolloProvider>
      <CharactersList />
    </ApolloProvider>
  );
};
CopiedCopy

See an example component.