- NPM
- Yarn
- pnpm
- esm.sh
yarn add @data-client/graphql
npm install --save @data-client/graphql
pnpm add @data-client/graphql
<script type="module">
import * from 'https://esm.sh/@data-client/graphql';
</script>
Define Endpoint and Schema
schema/endpoint.ts
export const gql = new GQLEndpoint('https://nosy-baritone.glitch.me');
export default gql;
- TypeScript
- JavaScript
schema/User.ts
import { GQLEntity } from '@data-client/graphql';
export default class User extends GQLEntity {
name: string | null = null;
email = '';
age = 0;
}
schema/User.ts
import { GQLEntity } from '@data-client/graphql';
export default class User extends GQLEntity {}
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
- Single
- List
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>
);
}
pages/UserList.tsx
import { useSuspense } from '@data-client/react';
import User from 'schema/User';
import gql from 'schema/endpoint';
const userList = gql.query(
`{
users {
id
name
email
}
}`,
{ users: [User] },
);
export default function UserList() {
const { users } = useSuspense(userList, {});
return (
<section>
{users.map(user => (
<UserSummary key={user.pk()} user={user} />
))}
</section>
);
}
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
import { GQLEndpoint, GQLEntity } from '@data-client/graphql'; 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.