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

feat(graphql): added Error Handler that will throw GraphQL errors #6506

Merged
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
5 changes: 5 additions & 0 deletions .changeset/clever-bikes-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@refinedev/graphql": minor
---

Added error handler that will throw GraphQL errors via Tanstack query from urql
54 changes: 43 additions & 11 deletions documentation/docs/data/packages/graphql/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,21 +290,51 @@ export const PostCreatePage () => {
}
```

## Authentication
### Handling Errors

When using GraphQL there are two kinds of errors we may see. Network errors and GraphQL errors from the API. URQL provides a _CombinedError_ class which holds these errors when encountered. The Refine GraphQL data provider package hooks identified these errors and provides them back along with a Refine specific error type for missing information. Each error type is prefixed with the `[type]` so that the you can determine how they can be handled.

- **Code errors** `[Code]`: Error is when a required parameter such as a Query or a Mutation has not been provided
- **Network errors** `[Network]`: Contains any error that would prevent making the network request.
- **GraphQL errors** `[GraphQL]` : Any errors recevied from the errors array from the GraphQL API, converted into a string.

The errors are provided through the Notification provider if used or through the Refine's error handlers.

If your API uses authentication, you can easily provide a custom fetcher for the requests and handle the authentication logic there. When creating a GraphQL Client, you can pass a `fetch` function to the client options. This function will be used to append the authentication headers to the requests.
### Managing Retries

TBA: https://commerce.nearform.com/open-source/urql/docs/advanced/authentication/
Refine uses Tanstack Query which by default retries the API call 3 times before showing the error to the user. For `[Code]` or `[GraphQL]` errors, these retries can be avoided, and the users would only need this for `[Network]` type errors using the `queryOptions` parameter passed into the data fetch hooks using a function call can help prevent non-required API calls.

```tsx
useList({
meta: {
gqlQuery: GET_LIST_QUERY,
},
// highlight-start
queryOptions: {
retry(failureCount, error) {
// failureCount provides the number of times the request has failed
// error is the error thrown from the GraphQL provider
return error?.message.includes("[Network]") && failureCount <= 3;
},
},
// highlight-end
});
```

## Authentication

If your API uses authentication, the easiest way of passing in the token through the header is via the **fetchOptions** parameter passed into the GraphQL Client.

```tsx title="data-provider.tsx"
import graphqlDataProvider, { GraphQLClient } from "@refinedev/graphql";
import createDataProvider from "@refinedev/graphql";
import { Client, fetchExchange } from "urql";

const client = new GraphQLClient(API_URL, {
fetch: (url: string, options: RequestInit) => {
return fetch(url, {
...options,
export const client = new Client({
url: API_URL,
exchanges: [fetchExchange],
fetchOptions: () => {
return {
headers: {
...options.headers,
/**
* For demo purposes, we're using `localStorage` to access the token.
* You can use your own authentication logic here.
Expand All @@ -313,7 +343,7 @@ const client = new GraphQLClient(API_URL, {
// highlight-next-line
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
};
},
});

Expand All @@ -323,6 +353,8 @@ const client = new GraphQLClient(API_URL, {
const dataProvider = graphqlDataProvider(client);
```

For more advanced authentication requirements you can use the urql _authExchange_ as documented in https://commerce.nearform.com/open-source/urql/docs/advanced/authentication

## Usage with Inferencer

You can also use `@refinedev/inferencer` package to generate sample codes for your views. Since the GraphQL data providers rely on `meta` fields, you'll need to provide some `meta` values beforehand and then Inferencer will use these values to infer the fields of the data provider's response, generate a code and a preview.
Expand All @@ -331,6 +363,6 @@ You can also use `@refinedev/inferencer` package to generate sample codes for yo

## Example

<CodeSandboxExample path="data-provider-nestjs-query" />
<CodeSandboxExample path="data-provider-graphql" />

[data-provider]: /docs/data/data-provider
23 changes: 23 additions & 0 deletions examples/data-provider-graphql/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
43 changes: 43 additions & 0 deletions examples/data-provider-graphql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<div align="center" style="margin: 30px;">
<a href="https://refine.dev/">
<img alt="refine logo" src="https://refine.ams3.cdn.digitaloceanspaces.com/readme/refine-readme-banner.png">
</a>

</br>
</br>

<div align="center">
<a href="https://refine.dev">Home Page</a> |
<a href="https://discord.gg/refine">Discord</a> |
<a href="https://refine.dev/examples/">Examples</a> |
<a href="https://refine.dev/blog/">Blog</a> |
<a href="https://refine.dev/docs/">Documentation</a>
</div>
</div>

</br>
</br>

<div align="center"><strong>Build your <a href="https://reactjs.org/">React</a>-based CRUD applications, without constraints.</strong><br>An open source, headless web application framework developed with flexibility in mind.

<br />
<br />

[![Discord](https://img.shields.io/discord/837692625737613362.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/refine)
[![Twitter Follow](https://img.shields.io/twitter/follow/refine_dev?style=social)](https://twitter.com/refine_dev)

<a href="https://www.producthunt.com/posts/refine-3?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-refine&#0045;3" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=362220&theme=light&period=daily" alt="refine - 100&#0037;&#0032;open&#0032;source&#0032;React&#0032;framework&#0032;to&#0032;build&#0032;web&#0032;apps&#0032;3x&#0032;faster | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>

</div>

## Try this example on your local

```bash
npm create refine-app@latest -- --example data-provider-graphql
```

## Try this example on CodeSandbox

<br/>

[![Open data-provider-graphql example from refine](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/embed/github/refinedev/refine/tree/master/examples/data-provider-graphql?view=preview&theme=dark&codemirror=1)
37 changes: 37 additions & 0 deletions examples/data-provider-graphql/graphql.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { IGraphQLConfig } from "graphql-config";

const config: IGraphQLConfig = {
schema: "https://api.nestjs-query.refine.dev/graphql",
extensions: {
codegen: {
hooks: {
afterOneFileWrite: ["eslint --fix", "prettier --write"],
},
generates: {
"src/graphql/schema.types.ts": {
plugins: ["typescript"],
config: {
skipTypename: true,
enumsAsTypes: true,
},
},
"src/graphql/types.ts": {
preset: "import-types",
documents: ["src/**/*.{ts,tsx}"],
plugins: ["typescript-operations"],
config: {
skipTypename: true,
enumsAsTypes: true,
preResolveTypes: false,
useTypeImports: true,
},
presetConfig: {
typesPath: "./schema.types",
},
},
},
},
},
};

export default config;
30 changes: 30 additions & 0 deletions examples/data-provider-graphql/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using refine" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<title>refine GraphQL example</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm run dev` or `yarn dev`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</html>
51 changes: 51 additions & 0 deletions examples/data-provider-graphql/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "data-provider-graphql",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc && refine build",
"codegen": "graphql-codegen",
"dev": "refine dev",
"refine": "refine",
"start": "refine start"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"@refinedev/antd": "^5.44.0",
"@refinedev/cli": "^2.16.39",
"@refinedev/core": "^4.56.0",
"@refinedev/graphql": "^7.0.0",
"@refinedev/react-router-v6": "^4.6.0",
"@uiw/react-md-editor": "^3.19.5",
"antd": "^5.17.0",
"graphql-ws": "^5.9.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.8.1",
"urql": "^4.2.1"
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/import-types-preset": "^3.0.0",
"@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-operations": "^4.0.1",
"@types/node": "^18.16.2",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^4.2.1",
"typescript": "^5.4.2",
"vite": "^5.1.6"
}
}
Binary file added examples/data-provider-graphql/public/favicon.ico
Binary file not shown.
15 changes: 15 additions & 0 deletions examples/data-provider-graphql/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"short_name": "refine hasura example",
"name": "refine hasura example",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
10 changes: 10 additions & 0 deletions examples/data-provider-graphql/public/refine.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading