This page demonstrates the steps to creating a client GraphQL using Apollo. For a full example scope, see learnbit-react.graphql
Run the following to create a component that will later implement the apollo provider:
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" } ] } }
Run the following to install the dependencies:
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>; };
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" } ] } }
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!
Run the following to create a component that will later implement the custom hook that fetches data from graphql:
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; }
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); } }
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); }); });
See the demo component for a full example that uses a mock provider.
Run the following to run your tests:
Create a new component:
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> ); }
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> ); };