Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: add section for TypeScript types generation with GraphQL Code Generator #2729

Merged
merged 3 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions docs/basics/typescript-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
---
title: TypeScript integration
order: 7
---

# URQL and TypeScript

URQL, with the help of [GraphQL Code Generator](https://www.the-guild.dev/graphql/codegen), can leverage the typed-design of GraphQL Schemas to generate TypeScript types on the flight.

## Getting started

### Installation

To get and running, install the following packages:

```sh
yarn add graphql
yarn add -D typescript @graphql-codegen/cli @graphql-codegen/client-preset
# or
npm install graphql
npm install -D typescript @graphql-codegen/cli @graphql-codegen/client-preset
```

Then, add the following script to your `package.json`:

```json
{
"scripts": {
"codegen": "graphql-codegen"
}
}
```

Now, let's create a configuration file for our current framework setup:

### Configuration

#### React project configuration

Create the following `codegen.ts` configuration file:

```ts
import { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
schema: '<YOUR_GRAPHQL_API_URL>',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
plugins: [],
},
},
};

export default config;
```

#### Vue project configuration

Create the following `codegen.ts` configuration file:

```ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
schema: '<YOUR_GRAPHQL_API_URL>',
documents: ['src/**/*.vue'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
config: {
useTypeImports: true,
},
plugins: [],
},
},
};

export default config;
```

#### Svelte project configuration

Create the following `codegen.ts` configuration file:

```ts
const config: CodegenConfig = {
schema: '<YOUR_GRAPHQL_API_URL>',
documents: ['src/**/*.svelte'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client',
plugins: [],
},
},
};

export default config;
```

## Typing queries, mutations and subscriptions

Now that your project is properly configured, let's start codegen in watch mode:

```sh
yarn codegen
# or
npm run codegen
```

This will generate a `./src/gql` folder that exposes a `graphql()` function.

Let's use this `graphql()` function to write our GraphQL Queries, Mutations and Subscriptions.

Here, an example with the React bindings, however, the usage remains the same for Vue and Svelte bindings:

```tsx
import React from 'react';
import { useQuery } from '@apollo/client';

import './App.css';
import Film from './Film';
import { graphql } from '../src/gql';

const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`);

function App() {
// `data` is typed!
const { data } = useQuery(allFilmsWithVariablesQueryDocument, { variables: { first: 10 } });
return (
<div className="App">
{data && (
<ul>
{data.allFilms?.edges?.map(
(e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />
)}
</ul>
)}
</div>
);
}

export default App;
```

_Examples with Vue are available [in the GraphQL Code Generator repository](https://github.com/dotansimha/graphql-code-generator/tree/master/examples/front-end/vue/urql)_.

Using the generated `graphql()` function to write your GraphQL document results in instantly typed result and variables for queries, mutations and subscriptions!
charlypoly marked this conversation as resolved.
Show resolved Hide resolved

Let's now see how to go further with GraphQL fragments.

## Getting further with Fragments

> Using GraphQL Fragments helps to explicitly declaring the data dependencies of your UI component and safely accessing only the data it needs.

Our `<Film>` component relies on the `FilmItem` definition, passed through the `film` props:

```tsx
// ...
import Film from './Film';
import { graphql } from '../src/gql';

const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`);

function App() {
// ...
return (
<div className="App">
{data && (
<ul>
{data.allFilms?.edges?.map(
(e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />
)}
</ul>
)}
</div>
);
}
// ...
```

GraphQL Code Generator generates type helpers to type your component props based on Fragments (for example, the `film=` prop) and retrieve your fragment's data (see example below).

Again, here is an example with the React bindings:

```tsx
import { FragmentType, useFragment } from './gql/fragment-masking';
import { graphql } from '../src/gql';

// again, we use the generated `graphql()` function to write GraphQL documents 👀
export const FilmFragment = graphql(/* GraphQL */ `
fragment FilmItem on Film {
id
title
releaseDate
producers
}
`);

const Film = (props: {
// `film` property has the correct type 🎉
film: FragmentType<typeof FilmFragment>;
}) => {
// `film` is of type `FilmFragment`, with no extraneous properties ⚡️
const film = useFragment(FilmFragment, props.film);
return (
<div>
<h3>{film.title}</h3>
<p>{film.releaseDate}</p>
</div>
);
};

export default Film;
```

_Examples with Vue are available [in the GraphQL Code Generator repository](https://github.com/dotansimha/graphql-code-generator/tree/master/examples/front-end/vue/urql)_.

You will notice that our `<Film>` component leverages 2 imports from our generated code (from `../src/gql`): the `FragmentType<T>` type helper and the `useFragment()` function.

- we use `FragmentType<typeof FilmFragment>` to get the corresponding Fragment TypeScript type
- later on, we use `useFragment()` to retrieve the properly film property
42 changes: 11 additions & 31 deletions docs/basics/ui-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,13 @@ const SearchResultPage = ({ variables, isLastPage, onLoadMore }) => {
</div>
))}
{isLastPage && todos.pageInfo.hasNextPage && (
<button
onClick={() => onLoadMore(todos.pageInfo.endCursor)}
>
load more
</button>
<button onClick={() => onLoadMore(todos.pageInfo.endCursor)}>load more</button>
)}
</>
)}
</div>
);
}
};

const Search = () => {
const [pageVariables, setPageVariables] = useState([
Expand All @@ -81,14 +77,12 @@ const Search = () => {
key={'' + variables.after}
variables={variables}
isLastPage={i === pageVariables.length - 1}
onLoadMore={after =>
setPageVariables([...pageVariables, { after, first: 10 }])
}
onLoadMore={after => setPageVariables([...pageVariables, { after, first: 10 }])}
/>
))}
</div>
);
}
};
```

Here we keep an array of all `variables` we've encountered and use them to render their
Expand Down Expand Up @@ -119,19 +113,15 @@ const Component = () => {
const client = useClient();
const router = useRouter();

const transitionPage = React.useCallback(async (id) => {
const transitionPage = React.useCallback(async id => {
const loadJSBundle = import('./page.js');
const loadData = client.query(TodoQuery, { id }).toPromise();
await Promise.all([loadJSBundle, loadData]);
router.push(`/todo/${id}`);
}, []);

return (
<button onClick={() => transitionPage('1')}>
Go to todo 1
</button>
)
}
return <button onClick={() => transitionPage('1')}>Go to todo 1</button>;
};
```

Here we're calling `client.query` to prepare a query when the transition begins.
Expand Down Expand Up @@ -160,12 +150,8 @@ const Component = () => {
const [result, fetch] = useQuery({ query: TodoQuery, pause: true });
const router = useRouter();

return (
<button onClick={fetch}>
Load todos
</button>
)
}
return <button onClick={fetch}>Load todos</button>;
};
```

We can unpause the hook to start fetching, or, like in this example, call its returned function to manually kick off the query.
Expand All @@ -188,14 +174,8 @@ import { refocusExchange } from '@urql/exchange-refocus';

const client = createClient({
url: 'some-url',
exchanges: [
dedupExchange,
refocusExchange(),
cacheExchange,
fetchExchange,
]
})
exchanges: [dedupExchange, refocusExchange(), cacheExchange, fetchExchange],
});
```

That's all we need to do to react to these patterns.