Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 469 lines 15 kB view raw view rendered
1--- 2title: Solid Bindings 3order: 3 4--- 5 6# Solid 7 8This guide covers how to install and setup `@urql/solid` and the `Client`, as well as query and mutate data with Solid. The `@urql/solid` package provides reactive primitives that integrate seamlessly with Solid's fine-grained reactivity system. 9 10> **Note:** This guide is for client-side SolidJS applications. If you're building a SolidStart application with SSR, see the [SolidStart guide](./solid-start.md) instead. The packages use different APIs optimized for their respective use cases. 11 12## Getting started 13 14### Installation 15 16Installing `@urql/solid` is quick and you won't need any other packages to get started with at first. We'll install the package with our package manager of choice. 17 18```sh 19yarn add @urql/solid graphql 20# or 21npm install --save @urql/solid graphql 22# or 23pnpm add @urql/solid graphql 24``` 25 26Most libraries related to GraphQL also need the `graphql` package to be installed as a peer dependency, so that they can adapt to your specific versioning requirements. That's why we'll need to install `graphql` alongside `@urql/solid`. 27 28Both the `@urql/solid` and `graphql` packages follow [semantic versioning](https://semver.org) and all `@urql/solid` packages will define a range of compatible versions of `graphql`. Watch out for breaking changes in the future however, in which case your package manager may warn you about `graphql` being out of the defined peer dependency range. 29 30### Setting up the `Client` 31 32The `@urql/solid` package exports a `Client` class from `@urql/core`, which we can use to create the GraphQL client. This central `Client` manages all of our GraphQL requests and results. 33 34```js 35import { createClient, cacheExchange, fetchExchange } from '@urql/solid'; 36 37const client = createClient({ 38 url: 'http://localhost:3000/graphql', 39 exchanges: [cacheExchange, fetchExchange], 40}); 41``` 42 43At the bare minimum we'll need to pass an API's `url` and `exchanges` when we create a `Client` to get started. 44 45Another common option is `fetchOptions`. This option allows us to customize the options that will be passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object, or a function returning an options object. 46 47In the following example we'll add a token to each `fetch` request that our `Client` sends to our GraphQL API. 48 49```js 50const client = createClient({ 51 url: 'http://localhost:3000/graphql', 52 exchanges: [cacheExchange, fetchExchange], 53 fetchOptions: () => { 54 const token = getToken(); 55 return { 56 headers: { authorization: token ? `Bearer ${token}` : '' }, 57 }; 58 }, 59}); 60``` 61 62### Providing the `Client` 63 64To make use of the `Client` in Solid we will have to provide it via Solid's Context API. This may be done with the help of the `Provider` export. 65 66```jsx 67import { render } from 'solid-js/web'; 68import { createClient, Provider, cacheExchange, fetchExchange } from '@urql/solid'; 69 70const client = createClient({ 71 url: 'http://localhost:3000/graphql', 72 exchanges: [cacheExchange, fetchExchange], 73}); 74 75const App = () => ( 76 <Provider value={client}> 77 <YourRoutes /> 78 </Provider> 79); 80 81render(() => <App />, document.getElementById('root')); 82``` 83 84Now every component inside and under the `Provider` can use GraphQL queries that will be sent to our API. 85 86## Queries 87 88The `@urql/solid` package offers a `createQuery` primitive that integrates with Solid's fine-grained reactivity system. 89 90### Run a first query 91 92For the following examples, we'll imagine that we're querying data from a GraphQL API that contains todo items. Let's dive right into it! 93 94```jsx 95import { Suspense, For } from 'solid-js'; 96import { gql } from '@urql/core'; 97import { createQuery } from '@urql/solid'; 98 99const TodosQuery = gql` 100 query { 101 todos { 102 id 103 title 104 } 105 } 106`; 107 108const Todos = () => { 109 const [result] = createQuery({ 110 query: TodosQuery, 111 }); 112 113 return ( 114 <Suspense fallback={<p>Loading...</p>}> 115 <ul> 116 <For each={result().data.todos}> 117 {(todo) => <li>{todo.title}</li>} 118 </For> 119 </ul> 120 </Suspense> 121 ); 122}; 123``` 124 125Here we have implemented our first GraphQL query to fetch todos. We see that `createQuery` accepts options and returns a tuple. In this case we've set the `query` option to our GraphQL query. The tuple we then get in return is an array where the first item is an accessor function that returns the result object. 126 127The result object contains several properties. The `fetching` field indicates whether the query is loading data, `data` contains the actual `data` from the API's result, and `error` is set when either the request to the API has failed or when our API result contained some `GraphQLError`s, which we'll get into later on the ["Errors" page](./errors.md). 128 129### Variables 130 131Typically we'll also need to pass variables to our queries, for instance, if we are dealing with pagination. For this purpose `createQuery` also accepts a `variables` option, which can be reactive. 132 133```jsx 134const TodosListQuery = gql` 135 query ($from: Int!, $limit: Int!) { 136 todos(from: $from, limit: $limit) { 137 id 138 title 139 } 140 } 141`; 142 143const Todos = (props) => { 144 const [result] = createQuery({ 145 query: TodosListQuery, 146 variables: () => ({ from: props.from, limit: props.limit }), 147 }); 148 149 // ... 150}; 151``` 152 153The `variables` option can be passed as a static object or as an accessor function that returns the variables. When using an accessor, the query will automatically re-execute when the variables change. 154 155```jsx 156import { Suspense, For, createSignal } from 'solid-js'; 157import { gql } from '@urql/core'; 158import { createQuery } from '@urql/solid'; 159 160const TodosListQuery = gql` 161 query ($from: Int!, $limit: Int!) { 162 todos(from: $from, limit: $limit) { 163 id 164 title 165 } 166 } 167`; 168 169const Todos = () => { 170 const [from, setFrom] = createSignal(0); 171 const limit = 10; 172 173 const [result] = createQuery({ 174 query: TodosListQuery, 175 variables: () => ({ from: from(), limit }), 176 }); 177 178 return ( 179 <div> 180 <Suspense fallback={<p>Loading...</p>}> 181 <ul> 182 <For each={result().data.todos}> 183 {(todo) => <li>{todo.title}</li>} 184 </For> 185 </ul> 186 </Suspense> 187 <button onClick={() => setFrom(f => f + 10)}>Next Page</button> 188 </div> 189 ); 190}; 191``` 192 193Whenever the variables change, `fetching` will switch to `true`, and a new request will be sent to our API, unless a result has already been cached previously. 194 195### Pausing `createQuery` 196 197In some cases we may want `createQuery` to execute a query when a pre-condition has been met, and not execute the query otherwise. For instance, we may be building a form and want a validation query to only take place when a field has been filled out. 198 199The `createQuery` primitive accepts a `pause` option that temporarily stops the query from executing. 200 201```jsx 202const Todos = (props) => { 203 const shouldPause = () => props.from == null || props.limit == null; 204 205 const [result] = createQuery({ 206 query: TodosListQuery, 207 variables: () => ({ from: props.from, limit: props.limit }), 208 pause: shouldPause, 209 }); 210 211 // ... 212}; 213``` 214 215Now whenever the mandatory variables aren't supplied the query won't be executed. This also means that `result().data` won't change, which means we'll still have access to our old data even though the variables may have changed. 216 217### Request Policies 218 219The `createQuery` primitive accepts a `requestPolicy` option that determines how results are retrieved from our `Client`'s cache. By default, this is set to `cache-first`, which means that we prefer to get results from our cache, but are falling back to sending an API request. 220 221Request policies aren't specific to `@urql/solid`, but are a common feature in urql's core. [You can learn more about how the cache behaves given the four different policies on the "Document Caching" page.](./document-caching.md) 222 223```jsx 224const [result] = createQuery({ 225 query: TodosListQuery, 226 variables: () => ({ from: props.from, limit: props.limit }), 227 requestPolicy: 'cache-and-network', 228}); 229``` 230 231The `requestPolicy` can be passed as a static string or as an accessor function. When using `cache-and-network`, the query will be refreshed from our API even after our cache has given us a cached result. 232 233### Context Options 234 235The `requestPolicy` option is part of urql's context options. In fact, there are several more built-in context options. These options can be passed via the `context` parameter. 236 237```jsx 238const [result] = createQuery({ 239 query: TodosListQuery, 240 variables: () => ({ from: props.from, limit: props.limit }), 241 context: () => ({ 242 requestPolicy: 'cache-and-network', 243 url: 'http://localhost:3000/graphql?debug=true', 244 }), 245}); 246``` 247 248[You can find a list of all `Context` options in the API docs.](../api/core.md#operationcontext) 249 250### Reexecuting Queries 251 252The `createQuery` primitive updates and executes queries automatically when reactive inputs change, but in some cases we may need to programmatically trigger a new query. This is the purpose of the second item in the tuple that `createQuery` returns. 253 254```jsx 255const Todos = () => { 256 const [result, reexecuteQuery] = createQuery({ 257 query: TodosListQuery, 258 variables: { from: 0, limit: 10 }, 259 }); 260 261 const refresh = () => { 262 // Refetch the query and skip the cache 263 reexecuteQuery({ requestPolicy: 'network-only' }); 264 }; 265 266 return ( 267 <div> 268 <Suspense fallback={<p>Loading...</p>}> 269 <ul> 270 <For each={result().data.todos}> 271 {(todo) => <li>{todo.title}</li>} 272 </For> 273 </ul> 274 </Suspense> 275 <button onClick={refresh}>Refresh</button> 276 </div> 277 ); 278}; 279``` 280 281Calling `refresh` in the above example will execute the query again forcefully, and will skip the cache, since we're passing `requestPolicy: 'network-only'`. 282 283## Mutations 284 285The `@urql/solid` package offers a `createMutation` primitive for executing GraphQL mutations. 286 287### Sending a mutation 288 289Let's again pick up an example with an imaginary GraphQL API for todo items. We'll set up a mutation that updates a todo item's title. 290 291```jsx 292import { gql } from '@urql/core'; 293import { createMutation } from '@urql/solid'; 294 295const UpdateTodo = gql` 296 mutation ($id: ID!, $title: String!) { 297 updateTodo (id: $id, title: $title) { 298 id 299 title 300 } 301 } 302`; 303 304const Todo = (props) => { 305 const [result, updateTodo] = createMutation(UpdateTodo); 306 307 const handleSubmit = (newTitle) => { 308 updateTodo({ id: props.id, title: newTitle }); 309 }; 310 311 return ( 312 <div> 313 <Show when={result().fetching}> 314 <p>Updating...</p> 315 </Show> 316 <Show when={result().error}> 317 <p>Error: {result().error.message}</p> 318 </Show> 319 {/* Your form UI here */} 320 </div> 321 ); 322}; 323``` 324 325Similar to `createQuery`, `createMutation` returns a tuple. The first item is an accessor that returns the result object containing `fetching`, `error`, and `data` — identical to query results. The second item is the execute function that triggers the mutation. 326 327Unlike `createQuery`, `createMutation` doesn't execute automatically. We must call the execute function with the mutation variables. 328 329### Using the mutation result 330 331The mutation result is available both through the reactive accessor and through the promise returned by the execute function. 332 333```jsx 334const Todo = (props) => { 335 const [result, updateTodo] = createMutation(UpdateTodo); 336 337 const handleSubmit = (newTitle) => { 338 const variables = { id: props.id, title: newTitle }; 339 340 updateTodo(variables).then((result) => { 341 // The result is almost identical to result() from the accessor 342 // It is an OperationResult. 343 if (!result.error) { 344 console.log('Todo updated!', result.data); 345 } 346 }); 347 }; 348 349 return ( 350 <div> 351 <Show when={result().fetching}> 352 <p>Updating...</p> 353 </Show> 354 {/* Your form UI here */} 355 </div> 356 ); 357}; 358``` 359 360The reactive accessor is useful when your UI needs to display progress on the mutation, and the returned promise is particularly useful for side effects that run after the mutation completes. 361 362### Handling mutation errors 363 364The promise returned by the execute function will never reject. Instead it will always return a promise that resolves to a result. 365 366If you're checking for errors, you should use `result.error`, which will be set to a `CombinedError` when any kind of errors occurred while executing your mutation. [Read more about errors on our "Errors" page.](./errors.md) 367 368```jsx 369const Todo = (props) => { 370 const [result, updateTodo] = createMutation(UpdateTodo); 371 372 const handleSubmit = (newTitle) => { 373 const variables = { id: props.id, title: newTitle }; 374 375 updateTodo(variables).then((result) => { 376 if (result.error) { 377 console.error('Oh no!', result.error); 378 } 379 }); 380 }; 381 382 // ... 383}; 384``` 385 386## Subscriptions 387 388The `@urql/solid` package offers a `createSubscription` primitive for handling GraphQL subscriptions with Solid's reactive system. 389 390### Setting up a subscription 391 392GraphQL subscriptions allow you to receive real-time updates from your GraphQL API. Here's an example of how to set up a subscription: 393 394```jsx 395import { gql } from '@urql/core'; 396import { createSubscription } from '@urql/solid'; 397 398const NewTodos = gql` 399 subscription { 400 newTodos { 401 id 402 title 403 } 404 } 405`; 406 407const TodoSubscription = () => { 408 const [result] = createSubscription({ 409 query: NewTodos, 410 }); 411 412 return ( 413 <div> 414 <Show when={result().fetching}> 415 <p>Waiting for updates...</p> 416 </Show> 417 <Show when={result().error}> 418 <p>Error: {result().error.message}</p> 419 </Show> 420 <Show when={result().data}> 421 <p>New todo: {result().data.newTodos.title}</p> 422 </Show> 423 </div> 424 ); 425}; 426``` 427 428### Handling subscription data 429 430Unlike queries and mutations, subscriptions can emit multiple results over time. You can use a `handler` function to accumulate or process subscription events: 431 432```jsx 433import { createSignal } from 'solid-js'; 434 435const TodoSubscription = () => { 436 const [todos, setTodos] = createSignal([]); 437 438 const handleSubscription = (previousData, newData) => { 439 setTodos(current => [...current, newData.newTodos]); 440 return newData; 441 }; 442 443 const [result] = createSubscription( 444 { 445 query: NewTodos, 446 }, 447 handleSubscription 448 ); 449 450 return ( 451 <ul> 452 <For each={todos()}> 453 {(todo) => <li>{todo.title}</li>} 454 </For> 455 </ul> 456 ); 457}; 458``` 459 460The handler function receives the previous data and the new data from the subscription, allowing you to accumulate results or transform them as needed. 461 462## Reading on 463 464This concludes the introduction for using `@urql/solid` with Solid. The rest of the documentation is mostly framework-agnostic and will apply to either `urql` in general, or the `@urql/core` package, which is the same between all framework bindings. Hence, next we may want to learn more about one of the following: 465 466- [How does the default "document cache" work?](./document-caching.md) 467- [How are errors handled and represented?](./errors.md) 468- [A quick overview of `urql`'s architecture and structure.](../architecture.md) 469- [Setting up other features, like authentication, uploads, or persisted queries.](../advanced/README.md)