Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

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

* doc(basics): restore original order

* doc(basics/typescript): wording

authored by

Charly POLY and committed by
GitHub
ecfb705d 29786b75

+257 -31
+246
docs/basics/typescript-integration.md
··· 1 + --- 2 + title: TypeScript integration 3 + order: 7 4 + --- 5 + 6 + # URQL and TypeScript 7 + 8 + 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. 9 + 10 + ## Getting started 11 + 12 + ### Installation 13 + 14 + To get and running, install the following packages: 15 + 16 + ```sh 17 + yarn add graphql 18 + yarn add -D typescript @graphql-codegen/cli @graphql-codegen/client-preset 19 + # or 20 + npm install graphql 21 + npm install -D typescript @graphql-codegen/cli @graphql-codegen/client-preset 22 + ``` 23 + 24 + Then, add the following script to your `package.json`: 25 + 26 + ```json 27 + { 28 + "scripts": { 29 + "codegen": "graphql-codegen" 30 + } 31 + } 32 + ``` 33 + 34 + Now, let's create a configuration file for our current framework setup: 35 + 36 + ### Configuration 37 + 38 + #### React project configuration 39 + 40 + Create the following `codegen.ts` configuration file: 41 + 42 + ```ts 43 + import { CodegenConfig } from '@graphql-codegen/cli'; 44 + 45 + const config: CodegenConfig = { 46 + schema: '<YOUR_GRAPHQL_API_URL>', 47 + documents: ['src/**/*.tsx'], 48 + ignoreNoDocuments: true, // for better experience with the watcher 49 + generates: { 50 + './src/gql/': { 51 + preset: 'client', 52 + plugins: [], 53 + }, 54 + }, 55 + }; 56 + 57 + export default config; 58 + ``` 59 + 60 + #### Vue project configuration 61 + 62 + Create the following `codegen.ts` configuration file: 63 + 64 + ```ts 65 + import type { CodegenConfig } from '@graphql-codegen/cli'; 66 + 67 + const config: CodegenConfig = { 68 + schema: '<YOUR_GRAPHQL_API_URL>', 69 + documents: ['src/**/*.vue'], 70 + ignoreNoDocuments: true, // for better experience with the watcher 71 + generates: { 72 + './src/gql/': { 73 + preset: 'client', 74 + config: { 75 + useTypeImports: true, 76 + }, 77 + plugins: [], 78 + }, 79 + }, 80 + }; 81 + 82 + export default config; 83 + ``` 84 + 85 + #### Svelte project configuration 86 + 87 + Create the following `codegen.ts` configuration file: 88 + 89 + ```ts 90 + const config: CodegenConfig = { 91 + schema: '<YOUR_GRAPHQL_API_URL>', 92 + documents: ['src/**/*.svelte'], 93 + ignoreNoDocuments: true, // for better experience with the watcher 94 + generates: { 95 + './src/gql/': { 96 + preset: 'client', 97 + plugins: [], 98 + }, 99 + }, 100 + }; 101 + 102 + export default config; 103 + ``` 104 + 105 + ## Typing queries, mutations and subscriptions 106 + 107 + Now that your project is properly configured, let's start codegen in watch mode: 108 + 109 + ```sh 110 + yarn codegen 111 + # or 112 + npm run codegen 113 + ``` 114 + 115 + This will generate a `./src/gql` folder that exposes a `graphql()` function. 116 + 117 + Let's use this `graphql()` function to write our GraphQL Queries, Mutations and Subscriptions. 118 + 119 + Here, an example with the React bindings, however, the usage remains the same for Vue and Svelte bindings: 120 + 121 + ```tsx 122 + import React from 'react'; 123 + import { useQuery } from '@apollo/client'; 124 + 125 + import './App.css'; 126 + import Film from './Film'; 127 + import { graphql } from '../src/gql'; 128 + 129 + const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ ` 130 + query allFilmsWithVariablesQuery($first: Int!) { 131 + allFilms(first: $first) { 132 + edges { 133 + node { 134 + ...FilmItem 135 + } 136 + } 137 + } 138 + } 139 + `); 140 + 141 + function App() { 142 + // `data` is typed! 143 + const { data } = useQuery(allFilmsWithVariablesQueryDocument, { variables: { first: 10 } }); 144 + return ( 145 + <div className="App"> 146 + {data && ( 147 + <ul> 148 + {data.allFilms?.edges?.map( 149 + (e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} /> 150 + )} 151 + </ul> 152 + )} 153 + </div> 154 + ); 155 + } 156 + 157 + export default App; 158 + ``` 159 + 160 + _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)_. 161 + 162 + Using the generated `graphql()` function to write your GraphQL document results in instantly typed result and variables for queries, mutations and subscriptions! 163 + 164 + Let's now see how to go further with GraphQL fragments. 165 + 166 + ## Getting further with Fragments 167 + 168 + > Using GraphQL Fragments helps to explicitly declaring the data dependencies of your UI component and safely accessing only the data it needs. 169 + 170 + Our `<Film>` component relies on the `FilmItem` definition, passed through the `film` props: 171 + 172 + ```tsx 173 + // ... 174 + import Film from './Film'; 175 + import { graphql } from '../src/gql'; 176 + 177 + const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ ` 178 + query allFilmsWithVariablesQuery($first: Int!) { 179 + allFilms(first: $first) { 180 + edges { 181 + node { 182 + ...FilmItem 183 + } 184 + } 185 + } 186 + } 187 + `); 188 + 189 + function App() { 190 + // ... 191 + return ( 192 + <div className="App"> 193 + {data && ( 194 + <ul> 195 + {data.allFilms?.edges?.map( 196 + (e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} /> 197 + )} 198 + </ul> 199 + )} 200 + </div> 201 + ); 202 + } 203 + // ... 204 + ``` 205 + 206 + 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). 207 + 208 + Again, here is an example with the React bindings: 209 + 210 + ```tsx 211 + import { FragmentType, useFragment } from './gql/fragment-masking'; 212 + import { graphql } from '../src/gql'; 213 + 214 + // again, we use the generated `graphql()` function to write GraphQL documents 👀 215 + export const FilmFragment = graphql(/* GraphQL */ ` 216 + fragment FilmItem on Film { 217 + id 218 + title 219 + releaseDate 220 + producers 221 + } 222 + `); 223 + 224 + const Film = (props: { 225 + // `film` property has the correct type 🎉 226 + film: FragmentType<typeof FilmFragment>; 227 + }) => { 228 + // `film` is of type `FilmFragment`, with no extraneous properties ⚡️ 229 + const film = useFragment(FilmFragment, props.film); 230 + return ( 231 + <div> 232 + <h3>{film.title}</h3> 233 + <p>{film.releaseDate}</p> 234 + </div> 235 + ); 236 + }; 237 + 238 + export default Film; 239 + ``` 240 + 241 + _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)_. 242 + 243 + 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. 244 + 245 + - we use `FragmentType<typeof FilmFragment>` to get the corresponding Fragment TypeScript type 246 + - later on, we use `useFragment()` to retrieve the properly film property
+11 -31
docs/basics/ui-patterns.md
··· 54 54 </div> 55 55 ))} 56 56 {isLastPage && todos.pageInfo.hasNextPage && ( 57 - <button 58 - onClick={() => onLoadMore(todos.pageInfo.endCursor)} 59 - > 60 - load more 61 - </button> 57 + <button onClick={() => onLoadMore(todos.pageInfo.endCursor)}>load more</button> 62 58 )} 63 59 </> 64 60 )} 65 61 </div> 66 62 ); 67 - } 63 + }; 68 64 69 65 const Search = () => { 70 66 const [pageVariables, setPageVariables] = useState([ ··· 81 77 key={'' + variables.after} 82 78 variables={variables} 83 79 isLastPage={i === pageVariables.length - 1} 84 - onLoadMore={after => 85 - setPageVariables([...pageVariables, { after, first: 10 }]) 86 - } 80 + onLoadMore={after => setPageVariables([...pageVariables, { after, first: 10 }])} 87 81 /> 88 82 ))} 89 83 </div> 90 84 ); 91 - } 85 + }; 92 86 ``` 93 87 94 88 Here we keep an array of all `variables` we've encountered and use them to render their ··· 119 113 const client = useClient(); 120 114 const router = useRouter(); 121 115 122 - const transitionPage = React.useCallback(async (id) => { 116 + const transitionPage = React.useCallback(async id => { 123 117 const loadJSBundle = import('./page.js'); 124 118 const loadData = client.query(TodoQuery, { id }).toPromise(); 125 119 await Promise.all([loadJSBundle, loadData]); 126 120 router.push(`/todo/${id}`); 127 121 }, []); 128 122 129 - return ( 130 - <button onClick={() => transitionPage('1')}> 131 - Go to todo 1 132 - </button> 133 - ) 134 - } 123 + return <button onClick={() => transitionPage('1')}>Go to todo 1</button>; 124 + }; 135 125 ``` 136 126 137 127 Here we're calling `client.query` to prepare a query when the transition begins. ··· 160 150 const [result, fetch] = useQuery({ query: TodoQuery, pause: true }); 161 151 const router = useRouter(); 162 152 163 - return ( 164 - <button onClick={fetch}> 165 - Load todos 166 - </button> 167 - ) 168 - } 153 + return <button onClick={fetch}>Load todos</button>; 154 + }; 169 155 ``` 170 156 171 157 We can unpause the hook to start fetching, or, like in this example, call its returned function to manually kick off the query. ··· 188 174 189 175 const client = createClient({ 190 176 url: 'some-url', 191 - exchanges: [ 192 - dedupExchange, 193 - refocusExchange(), 194 - cacheExchange, 195 - fetchExchange, 196 - ] 197 - }) 177 + exchanges: [dedupExchange, refocusExchange(), cacheExchange, fetchExchange], 178 + }); 198 179 ``` 199 180 200 181 That's all we need to do to react to these patterns. 201 -