Skip to main content

GraphQL Usage

npm install --save @data-client/graphql

Define Endpoint and Schema

schema/endpoint.ts
export const gql = new GQLEndpoint('https://nosy-baritone.glitch.me');
export default gql;
schema/User.ts
import { GQLEntity } from '@data-client/graphql';

export default class User extends GQLEntity {
readonly name: string | null = null;
readonly email: string = '';
readonly age: number = 0;
}

Entitys are immutable. Use readonly in typescript to enforce this.

tip

Using GQLEntities is not required, but is important to achieve data consistency.

Query the Graph

pages/UserDetail.tsx
import { useSuspense } from '@data-client/react';
import User from 'schema/User';
import gql from 'schema/endpoint';

export const userDetail = gql.query(
(v: { name: string }) => `query UserDetail($name: String!) {
user(name: $name) {
id
name
email
}
}`,
{ user: User },
);

export default function UserDetail({ name }: { name: string }) {
const { user } = useSuspense(userDetail, { name });
return (
<article>
<h2>{user.name}</h2>
<div>{user.email}</div>
</article>
);
}

useSuspense() guarantees access to data with sufficient freshness. This means it may issue network calls, and it may suspend until the fetch completes. Param changes will result in accessing the appropriate data, which also sometimes results in new network calls and/or suspends.

  • Fetches are centrally controlled, and thus automatically deduplicated
  • Data is centralized and normalized guaranteeing consistency across uses, even with different endpoints.
    • (For example: navigating to a detail page with a single entry from a list view will instantly show the same data as the list without requiring a refetch.)
SWAPI Demo
const gql = new GQLEndpoint(
  'https://swapi-graphql.netlify.app/.netlify/functions/index',
);
class Person extends GQLEntity {
  readonly id: string = '';
  readonly name: string = '';
  readonly height: string = '';
}
const PageInfo = {
  hasNextPage: false,
  startCursor: '',
  endCursor: '',
}
const allPeople = gql.query(
  (v: { first?: number; after?: string }) => `
query People($first: Int, $after:String) {
  allPeople(first: $first, after:$after) {
    people{
      id,name,height
    },
    pageInfo {
      hasNextPage,
      startCursor,
      endCursor
    }
  }
}
`,
{ allPeople: { people: [Person], pageInfo: PageInfo } },
);
function StarPeople() {
  const { people, pageInfo } = useSuspense(allPeople, { first: 5 }).allPeople;
  return (
    <div>
      {people.map(person => (
        <div key={person.id}>
          name: {person.name} height: {person.height}
        </div>
      ))}
    </div>
  );
}
render(<StarPeople/>);
🔴 Live Preview
Store

Mutate the Graph

We're using SWAPI as our example, since it offers mutations.

pages/CreateReview.tsx
import { useController } from '@data-client/react';
import { GQLEndpoint, GQLEntity } from '@data-client/graphql';

const gql = new GQLEndpoint(
'https://swapi-graphql.netlify.app/.netlify/functions/index',
);

class Review extends GQLEntity {
readonly stars: number = 0;
readonly commentary: string = '';
}

const createReview = gql.mutation(
(v: {
ep: string;
review: { stars: number; commentary: string };
}) => `mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}`,
{ createReview: Review },
);

export default function NewReviewForm() {
const ctrl = useController();
return (
<Form onSubmit={e => ctrl.fetch(createReview, new FormData(e.target))}>
<FormField name="ep" />
<FormField name="review" type="compound" />
</Form>
);
}

The first argument to GQLEndpoint.query or GQLEndpoint.mutate is either the query string or a function that returns the query string. The main value of using the latter is enforcing the function argument types.