├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── __tests__
├── init-urql-client.spec.ts
└── with-urql-client.spec.tsx
├── assets
├── next_logo.png
└── urql_logo.png
├── examples
├── 1-with-urql-client
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ │ └── pokemon_list.tsx
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ └── index.tsx
│ ├── public
│ │ └── types
│ │ │ ├── Bug@2x.png
│ │ │ ├── Dark@2x.png
│ │ │ ├── Dragon@2x.png
│ │ │ ├── Electric@2x.png
│ │ │ ├── Fairy@2x.png
│ │ │ ├── Fight@2x.png
│ │ │ ├── Fire@2x.png
│ │ │ ├── Flying@2x.png
│ │ │ ├── Ghost@2x.png
│ │ │ ├── Grass@2x.png
│ │ │ ├── Ground@2x.png
│ │ │ ├── Ice@2x.png
│ │ │ ├── Normal@2x.png
│ │ │ ├── Poison@2x.png
│ │ │ ├── Psychic@2x.png
│ │ │ ├── Rock@2x.png
│ │ │ ├── Steel@2x.png
│ │ │ └── Water@2x.png
│ ├── tsconfig.json
│ └── yarn.lock
├── 2-with-_app.js
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ │ └── pokemon_list.tsx
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ └── index.tsx
│ ├── public
│ │ ├── favicon.ico
│ │ └── types
│ │ │ ├── Bug@2x.png
│ │ │ ├── Dark@2x.png
│ │ │ ├── Dragon@2x.png
│ │ │ ├── Electric@2x.png
│ │ │ ├── Fairy@2x.png
│ │ │ ├── Fight@2x.png
│ │ │ ├── Fire@2x.png
│ │ │ ├── Flying@2x.png
│ │ │ ├── Ghost@2x.png
│ │ │ ├── Grass@2x.png
│ │ │ ├── Ground@2x.png
│ │ │ ├── Ice@2x.png
│ │ │ ├── Normal@2x.png
│ │ │ ├── Poison@2x.png
│ │ │ ├── Psychic@2x.png
│ │ │ ├── Rock@2x.png
│ │ │ ├── Steel@2x.png
│ │ │ └── Water@2x.png
│ ├── tsconfig.json
│ └── yarn.lock
└── 3-with-custom-exchange
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ └── pokemon_list.tsx
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ └── index.tsx
│ ├── public
│ ├── favicon.ico
│ └── types
│ │ ├── Bug@2x.png
│ │ ├── Dark@2x.png
│ │ ├── Dragon@2x.png
│ │ ├── Electric@2x.png
│ │ ├── Fairy@2x.png
│ │ ├── Fight@2x.png
│ │ ├── Fire@2x.png
│ │ ├── Flying@2x.png
│ │ ├── Ghost@2x.png
│ │ ├── Grass@2x.png
│ │ ├── Ground@2x.png
│ │ ├── Ice@2x.png
│ │ ├── Normal@2x.png
│ │ ├── Poison@2x.png
│ │ ├── Psychic@2x.png
│ │ ├── Rock@2x.png
│ │ ├── Steel@2x.png
│ │ └── Water@2x.png
│ ├── tsconfig.json
│ ├── utils
│ └── url-exchange.ts
│ └── yarn.lock
├── jest.config.js
├── package.json
├── rollup.config.js
├── src
├── index.ts
├── init-urql-client.ts
├── types.ts
└── with-urql-client.tsx
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env", "@babel/typescript"],
3 | "plugins": [
4 | "@babel/proposal-object-rest-spread",
5 | [
6 | "babel-plugin-transform-async-to-promises",
7 | {
8 | "inlineHelpers": true,
9 | "externalHelpers": true
10 | }
11 | ]
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | extends: [
4 | 'plugin:@typescript-eslint/recommended',
5 | 'plugin:react/recommended',
6 | 'plugin:prettier/recommended',
7 | ],
8 | parserOptions: {
9 | ecmaVersion: 2018,
10 | sourceType: 'module',
11 | },
12 | plugins: ['react-hooks'],
13 | rules: {
14 | '@typescript-eslint/no-explicit-any': 'off',
15 | '@typescript-eslint/explicit-function-return-type': 'off',
16 | 'react/prop-types': 'off',
17 | 'react-hooks/rules-of-hooks': 'error',
18 | 'react-hooks/exhaustive-deps': 'warn',
19 | },
20 | settings: {
21 | react: {
22 | version: 'detect',
23 | },
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | next-urql.tgz
5 | .vscode
6 | coverage
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/src/*.{ts,tsx,d.ts}
3 | !/dist/*.{ts,tsx,d.ts,js.js.map}
4 | !index.d.ts
5 | !react-ssr-prepass.d.ts
6 | !/package.json
7 | !/README.md
8 | !LICENSE
9 | !CHANGELOG.md
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 80,
6 | tabWidth: 2,
7 | };
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | notifications:
2 | email: false
3 |
4 | branches:
5 | only:
6 | - master
7 | - develop
8 | - /^v\d+\.\d+\.\d+/
9 |
10 | language: node_js
11 |
12 | node_js:
13 | - 10
14 | - 12
15 |
16 | install:
17 | - yarn install --frozen-lockfile --non-interactive
18 |
19 | jobs:
20 | include:
21 | - &checkTs
22 | stage: Check TypeScript
23 | script:
24 | - yarn check:types
25 | - <<: *checkTs
26 | node_js: 12
27 | - &lint
28 | stage: Lint
29 | script:
30 | - yarn lint
31 | - <<: *lint
32 | node_js: 12
33 | - &checkFormatting
34 | stage: Check Formatting
35 | script:
36 | - yarn check:format
37 | - <<: *checkFormatting
38 | node_js: 12
39 | - &build
40 | stage: Build
41 | script:
42 | - yarn build
43 | - <<: *build
44 | node_js: 12
45 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [0.3.0] - 2020-02-20
9 |
10 | This release adds support for different `urql` Client configurations between the client-side and the server-side when using `next-urql`.
11 |
12 | **Warning:** To support this, access to Next's context object, `ctx`, **can only happen on the server**.
13 |
14 | ### Added
15 |
16 | - An example showing how to use a custom exchange with `next-urql`. PR by @ryan-gilb [here](https://github.com/FormidableLabs/next-urql/pull/32).
17 | - Instructions for using `next-urql` with ReasonML. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/28).
18 |
19 | ### Fixed
20 |
21 | - `clientOptions` are no longer serialized inside of `withUrql`'s `getInitialProps` method. This ensures that users can use different Client configurations between the client and server. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/33).
22 | - Proper support for forwarding `pageProps` when using `withUrqlClient` with an `_app.js` component. The `urql` Client instance is also attached to `ctx` for `_app.js` `getInitialProps`. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/38).
23 | - `react-ssr-prepass` dependency upgraded to `1.1.2` to support `urql` `>= 1.9.0`. PR by @JoviDeCroock [here](https://github.com/FormidableLabs/next-urql/pull/37).
24 |
25 | ### Diff
26 |
27 | https://github.com/FormidableLabs/next-urql/compare/v0.2.5...v0.3.0
28 |
29 | ## [0.2.5] - 2020-01-20
30 |
31 | This release encompasses small changes to our TypeScript definitions for `next-urql`, with an upgrade to using `next@9` as the basis for new type definitions in lieu of `@types/next`. The examples were also converted over to TypeScript from JavaScript.
32 |
33 | ### Added
34 |
35 | - All example projects now use TypeScript 🎉 PRs by @ryan-gilb [here](https://github.com/FormidableLabs/next-urql/pull/19) and [here](https://github.com/FormidableLabs/next-urql/pull/21). This gives us stronger guarantees around library types.
36 |
37 | ### Fixed
38 |
39 | - Upgraded type definitions to use types from `next@9`. PR by @ryan-gilb [here](https://github.com/FormidableLabs/next-urql/pull/22). If accessing the `NextContextWithAppTree` `interface`, the name has changed to `NextUrqlContext`.
40 |
41 | ### Diff
42 |
43 | https://github.com/FormidableLabs/next-urql/compare/v0.2.4...v0.2.5
44 |
45 | ## [0.2.4] - 2020-01-08
46 |
47 | This release adds support for accessing the `urqlClient` instance off of Next's context object.
48 |
49 | ### Added
50 |
51 | - `urqlClient` is now added to Next's context object, `ctx`, such that it can be accessed by other components lower in the tree. PR by @BjoernRave [here](https://github.com/FormidableLabs/next-urql/pull/15).
52 |
53 | ### Diff
54 |
55 | https://github.com/FormidableLabs/next-urql/compare/v0.2.3...v0.2.4
56 |
57 | ## [0.2.3] - 2019-12-31
58 |
59 | This release fixes support for using `withUrqlClient` with `_app.js`.
60 |
61 | ### Added
62 |
63 | - Examples are now separated into an `examples` directory. The first, `1-with-urql-client`, shows recommended usage by wrapping a Page component, while the second, `2-with-_app.js` shows how to set up `next-urql` with `_app.js`.
64 |
65 | ### Fixed
66 |
67 | - Be sure to check for `urqlClient` in both direct props and `pageProps` to handle `_app.js` usage with `withUrqlClient`. PR by @bmathews and @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/13).
68 |
69 | ### Diff
70 |
71 | https://github.com/FormidableLabs/next-urql/compare/v0.2.2...v0.2.3
72 |
73 | ## [0.2.2] - 2019-12-17
74 |
75 | This release fixes a small discrepancy in the types used by `withUrqlClient` and the public API defined by our `index.d.ts` file.
76 |
77 | ### Fixed
78 |
79 | - Use `NextUrqlClientConfig` in lieu of `NextUrqlClientOptions` in `index.d.ts` to match implementation of `withUrqlClient`. PR by @kylealwyn [here](https://github.com/FormidableLabs/next-urql/pull/9).
80 |
81 | ### Diff
82 |
83 | https://github.com/FormidableLabs/next-urql/compare/v0.2.1...v0.2.2
84 |
85 | ## [0.2.1] - 2019-12-17
86 |
87 | This release fixes a regression introduced in 0.2.0 involving circular structures created bt `withUrqlClient`'s `getInitialProps` method.
88 |
89 | ### Fixed
90 |
91 | - Amend circular structure in `withUrqlClient` caused by returning `ctx` in `getInitialProps`. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/7).
92 | - Fix dependency resolution issues in the `example` project. Update `example` documentation. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/7).
93 |
94 | ### Diff
95 |
96 | https://github.com/FormidableLabs/next-urql/compare/v0.2.0...v0.2.1
97 |
98 | ## [0.2.0] - 2019-12-08 [Deprecated]
99 |
100 | This release adds support for accessing Next's context object, `ctx`, to instantiate your `urql` Client instance.
101 |
102 | ### Added
103 |
104 | - Support for accessing Next's context object, `ctx`, when initializing `withUrqlClient` and creating client options. This should assist users who need to access some data stored in `ctx` to instantiate their `urql` Client instance. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/4).
105 |
106 | ### Diff
107 |
108 | https://github.com/FormidableLabs/next-urql/compare/v0.1.1...v0.2.0
109 |
110 | ## [0.1.1] - 2019-11-11
111 |
112 | This release adds TypeScript definitions to `next-urql`, alongside important pieces like a License (MIT), and improved documentation for users and contributors.
113 |
114 | ### Added
115 |
116 | - TypeScript definitions for the public API of `next-urql` now ship with the library. PR by @parkerziegler [here](https://github.com/FormidableLabs/next-urql/pull/2).
117 | - MIT License.
118 | - Improved README documentation around `withUrqlClient` usage.
119 | - CONTRIBUTING.md to help new contributors to the project get involved.
120 |
121 | ### Diff
122 |
123 | https://github.com/FormidableLabs/next-urql/compare/v0.1.0...v0.1.1
124 |
125 | ## [0.1.0] - 2019-11-02
126 |
127 | This is the initial release of `next-urql` in its Beta API. The package is not meant to be consumed yet, and this purely serves as a prerelease for local testing.
128 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for contributing!
4 |
5 | ## Development
6 |
7 | ### Installing dependencies
8 |
9 | We use [`yarn`](https://yarnpkg.com/en/docs/getting-started).
10 |
11 | Install all dependencies by running:
12 |
13 | ```sh
14 | $ yarn
15 | ```
16 |
17 | ### Running the Example Project
18 |
19 | We have an in repo example project in the `/example` directory that can be used to test local changes to `next-urql`. The example works by packing a tarball of the `src` directory using `yarn pack`. All you need to do to get up and running is:
20 |
21 | ```sh
22 | # Navigate to the example directory.
23 | cd example
24 |
25 | # Run the automated install script.
26 | # This will handle setting up your dependencies correctly.
27 | bash install_deps.sh
28 |
29 | # Start the development server.
30 | yarn dev
31 | ```
32 |
33 | You can also follow the instructions in the example project's [`README`](/example/README.md).
34 |
35 | ### Testing
36 |
37 | Run tests using `yarn test`.
38 |
39 | ### Linting, Formatting, and Type Checking
40 |
41 | To lint your code from the command line:
42 |
43 | ```sh
44 | yarn lint
45 | ```
46 |
47 | To run Prettier over files from the command line and edit them in place:
48 |
49 | ```sh
50 | yarn format
51 | ```
52 |
53 | To check types:
54 |
55 | ```sh
56 | yarn check:types
57 | ```
58 |
59 | ### Before submitting a PR
60 |
61 | Thanks for taking the time to help us make `next-urql` even better! Before you go ahead and submit a PR, make sure that you have done the following:
62 |
63 | - Consider if your changes should be incorporated in the current example project or a new one. Like a new feature, option, etc. Let's try out everything we add!
64 | - Add an `## UNRELEASED` `CHANGELOG.md` entry for later publishing ease.
65 | - Check that all of the examples build: `yarn build-examples`.
66 | - Run all checks using `yarn run check`
67 |
68 | ### Releasing a new version to NPM
69 |
70 | _Only for project administrators_.
71 |
72 | 1. Update `CHANGELOG.md`, following format for previous versions
73 | 2. Commit as "Changes for version VERSION"
74 | 3. Run `npm version patch` (or `minor|major|VERSION`) to run tests and lint,
75 | build published directories, then update `package.json` + add a git tag.
76 | 4. Run `npm publish` and publish to NPM if all is well.
77 | 5. Run `git push && git push --tags`
78 |
79 | ## Contributor Covenant Code of Conduct
80 |
81 | ### Our Pledge
82 |
83 | In the interest of fostering an open and welcoming environment, we as
84 | contributors and maintainers pledge to making participation in our project and
85 | our community a harassment-free experience for everyone, regardless of age, body
86 | size, disability, ethnicity, gender identity and expression, level of experience,
87 | nationality, personal appearance, race, religion, or sexual identity and
88 | orientation.
89 |
90 | ### Our Standards
91 |
92 | Examples of behavior that contributes to creating a positive environment
93 | include:
94 |
95 | - Using welcoming and inclusive language
96 | - Being respectful of differing viewpoints and experiences
97 | - Gracefully accepting constructive criticism
98 | - Focusing on what is best for the community
99 | - Showing empathy towards other community members
100 |
101 | Examples of unacceptable behavior by participants include:
102 |
103 | - The use of sexualized language or imagery and unwelcome sexual attention or
104 | advances
105 | - Trolling, insulting/derogatory comments, and personal or political attacks
106 | - Public or private harassment
107 | - Publishing others' private information, such as a physical or electronic
108 | address, without explicit permission
109 | - Other conduct which could reasonably be considered inappropriate in a
110 | professional setting
111 |
112 | ### Our Responsibilities
113 |
114 | Project maintainers are responsible for clarifying the standards of acceptable
115 | behavior and are expected to take appropriate and fair corrective action in
116 | response to any instances of unacceptable behavior.
117 |
118 | Project maintainers have the right and responsibility to remove, edit, or
119 | reject comments, commits, code, wiki edits, issues, and other contributions
120 | that are not aligned to this Code of Conduct, or to ban temporarily or
121 | permanently any contributor for other behaviors that they deem inappropriate,
122 | threatening, offensive, or harmful.
123 |
124 | ### Scope
125 |
126 | This Code of Conduct applies both within project spaces and in public spaces
127 | when an individual is representing the project or its community. Examples of
128 | representing a project or community include using an official project e-mail
129 | address, posting via an official social media account, or acting as an appointed
130 | representative at an online or offline event. Representation of a project may be
131 | further defined and clarified by project maintainers.
132 |
133 | ### Enforcement
134 |
135 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
136 | reported by contacting the project team at parker.ziegler@formidable.com. All
137 | complaints will be reviewed and investigated and will result in a response that
138 | is deemed necessary and appropriate to the circumstances. The project team is
139 | obligated to maintain confidentiality with regard to the reporter of an incident.
140 | Further details of specific enforcement policies may be posted separately.
141 |
142 | Project maintainers who do not follow or enforce the Code of Conduct in good
143 | faith may face temporary or permanent repercussions as determined by other
144 | members of the project's leadership.
145 |
146 | ### Attribution
147 |
148 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
149 | available at [http://contributor-covenant.org/version/1/4][version]
150 |
151 | [homepage]: http://contributor-covenant.org
152 | [version]: http://contributor-covenant.org/version/1/4/
153 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Formidable
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Project Moved
2 |
3 | As of March 7, 2020, `next-urql` has been moved into a core package in the [`urql` monorepo](https://github.com/FormidableLabs/urql). In an effort to better consolidate and centralize work on the extended `urql` ecosystem, we at Formidable feels it makes sense to group these projects under a single umbrella. This also promotes better discoverability for contributors and future maintainers.
4 |
5 | All new issues, pull requests, and code discussion for this project should happen there. All new releases will be published from that package. Thanks so much for using `next-urql`!
6 |
7 |
18 |
19 | ## `next-urql`
20 |
21 | A set of convenience utilities for using `urql` with NextJS.
22 |
23 | ### Motivation
24 |
25 | Using GraphQL with server-side rendering in React is a challenging problem. Currently, React has no support for `Suspense` for data fetching on the server. To get around this, a prepass step can be used to walk the tree (or a subsection of the tree) of your React application and suspend when it encounters thrown `Promise`s. For more information, check out [`react-ssr-prepass`](https://github.com/FormidableLabs/react-ssr-prepass).
26 |
27 | `next-urql` handles integrating this prepass step for you, such that your NextJS application using `urql` will prefetch your GraphQL queries on the server before sending down markup to the Client.
28 |
29 | ### Installation
30 |
31 | Install `next-urql` along with its `peerDependencies`.
32 |
33 | ```sh
34 | npm install --save next-urql urql react-is isomorphic-unfetch
35 | ```
36 |
37 | `react-is`, `styled-components`, and `isomorphic-unfetch` help to support server-side `Suspense` with `react-ssr-prepass`. This assumes you have followed the basic installation steps for `urql` [here](https://github.com/FormidableLabs/urql#installation).
38 |
39 | ### Usage
40 |
41 | To use `next-urql`, first `import` the `withUrqlClient` higher order component.
42 |
43 | ```javascript
44 | import { withUrqlClient } from 'next-urql';
45 | ```
46 |
47 | Then, for any page in your `pages` directory for which you want to prefetch GraphQL queries, wrap the page in `withUrqlClient`. For example, let's say you have an `index.js` page that renders two components that make GraphQL requests using `urql`, `PokemonList` and `PokemonTypes`. To run their queries initially on the server-side you'd do something like the following:
48 |
49 | ```javascript
50 | import React from 'react';
51 | import Head from 'next/head';
52 | import { withUrqlClient } from 'next-urql';
53 |
54 | import PokemonList from '../components/pokemon_list';
55 | import PokemonTypes from '../components/pokemon_types';
56 |
57 | const Root = () => (
58 |
59 |
60 | Root
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 |
69 | export default withUrqlClient({ url: 'https://graphql-pokemon.now.sh' })(Root);
70 | ```
71 |
72 | Read more below in the [API](#API) section to learn more about the arguments that can be passed to `withUrqlClient`.
73 |
74 | #### Integration with `_app.js`
75 |
76 | Next allows you to override the root of your application using a special page called [`_app.js`](https://nextjs.org/docs#custom-app). If you want to have all GraphQL requests in your application fetched on the server-side, you _could_ wrap the component exported by `_app.js` in `withUrqlClient`. However, be aware that this will opt you out of [automatic static optimization](https://nextjs.org/docs#automatic-static-optimization) for your entire application. In general, it's recommended practice to only use `withUrqlClient` on the pages that have GraphQL operations in their component tree. Read more in the [Caveats](#Caveats) section. Check out our example for using `next-urql` with `_app.js` [here](/examples/2-with-_app.js/README.md).
77 |
78 | ### API
79 |
80 | `next-urql` exposes a single higher order component, `withUrqlClient`. This HoC accepts two arguments:
81 |
82 | #### `clientOptions` (Required)
83 |
84 | The `clientOptions` argument is required. It represents all of the options you want to enable on your `urql` Client instance. It has the following union type:
85 |
86 | ```typescript
87 | type NextUrqlClientConfig =
88 | | Omit
89 | | ((ctx: NextPageContext) => Omit);
90 | ```
91 |
92 | The `ClientOptions` `interface` comes from `urql` itself and has the following type:
93 |
94 | ```typescript
95 | interface ClientOptions {
96 | /** The GraphQL endpoint your application is using. */
97 | url: string;
98 | /** Any additional options to pass to fetch. */
99 | fetchOptions?: RequestInit | (() => RequestInit);
100 | /** An alternative fetch implementation. */
101 | fetch?: typeof fetch;
102 | /** The exchanges used by the Client. See mergeExchanges below for information on modifying exchanges in next-urql. */
103 | exchanges?: Exchange[];
104 | /** A flag to enable suspense on the server. next-urql handles this for you. */
105 | suspense?: boolean;
106 | /** The default request policy for requests. */
107 | requestPolicy?: RequestPolicy;
108 | /** Use HTTP GET for queries. */
109 | preferGetMethod?: boolean;
110 | /** Mask __typename from results. */
111 | maskTypename?: boolean;
112 | }
113 | ```
114 |
115 | This means you have two options for creating your `urql` Client. The first involves just passing the options as an object directly:
116 |
117 | ```typescript
118 | withUrqlClient({
119 | url: 'http://localhost:3000',
120 | fetchOptions: {
121 | referrer: 'no-referrer',
122 | redirect: 'follow',
123 | },
124 | });
125 | ```
126 |
127 | The second involves passing a function, which receives Next's context object, `ctx`, as an argument and returns `urql`'s Client options. This is helpful if you need to access some part of Next's context to instantiate your Client options. **Note: `ctx` is _only_ available on the initial server-side render and _not_ on client-side navigation**. This is necessary to allow for different Client configurations between server and client.
128 |
129 | ```typescript
130 | withUrqlClient(ctx => ({
131 | url: 'http://localhost:3000',
132 | fetchOptions: {
133 | headers: {
134 | Authorization: ctx
135 | ? `Bearer ${ctx?.req?.headers?.Authorization ?? ''}`
136 | : localStorage.getItem('token') ?? '',
137 | },
138 | },
139 | }));
140 | ```
141 |
142 | In client-side SPAs using `urql`, you typically configure the Client yourself and pass it as the `value` prop to `urql`'s context `Provider`. `withUrqlClient` handles setting all of this up for you under the hood. By default, you'll be opted into server-side `Suspense` and have the necessary `exchanges` set up for you, including the [`ssrExchange`](https://formidable.com/open-source/urql/docs/api/#ssrexchange-exchange-factory). If you need to customize your exchanges beyond the defaults `next-urql` provides, use the second argument to `withUrqlClient`, `mergeExchanges`.
143 |
144 | #### `mergeExchanges` (Optional)
145 |
146 | The `mergeExchanges` argument is optional. This is a function that takes the `ssrExchange` created by `next-urql` as its only argument and allows you to configure your exchanges as you wish. It has the following type signature:
147 |
148 | ```typescript
149 | (ssrExchange: SSRExchange) => Exchange[]
150 | ```
151 |
152 | By default, `next-urql` will incorprate the `ssrExchange` into your `exchanges` array in the correct location (after any other caching exchanges, but _before_ the `fetchExchange` – read more [here](https://formidable.com/open-source/urql/docs/basics/#setting-up-the-client)). Use this argument if you want to configure your Client with additional custom `exchanges`, or access the `ssrCache` directly to extract or restore data from its cache.
153 |
154 | ### Different Client configurations on the client and the server
155 |
156 | There are use cases where you may need different configurations for your `urql` Client on the client-side and the server-side; for example, you may want to interact with one GraphQL endpoint on the server-side and another on the client-side. `next-urql` supports this as of v0.3.0. We recommend using `typeof window === 'undefined'` or a `process.browser` check.
157 |
158 | ```typescript
159 | withUrqlClient({
160 | url:
161 | typeof window === 'undefined'
162 | ? 'https://my-server-graphql-api.com/graphql'
163 | : 'https://my-client-graphql-api.com/graphql',
164 | });
165 | ```
166 |
167 | If you want more customization of your Client instance to modify requests on specific routes, for instance, consider using a custom exchange. You can find an example of that [here](/examples/3-with-custom-exchange/README.md).
168 |
169 | ### Accessing the `urql` Client inside a Page component's `getInitialProps` method
170 |
171 | There are use cases where you may want to access the `urql` Client instance inside your Page component's `getInitialProps` method. To support this, we actually attach the `urql` Client instance to the `ctx` object passed to your Page's `getInitialProps` method. You can access it like so:
172 |
173 | ```typescript
174 | import { NextUrqlPageContext } from 'next-urql';
175 |
176 | const Page = () => {
177 | return ;
178 | };
179 |
180 | Page.getInitialProps = async (ctx: NextUrqlPageContext) => {
181 | // Do something with the urql Client instance!
182 | let client = ctx.urqlClient;
183 |
184 | return {
185 | ...props,
186 | };
187 | };
188 | ```
189 |
190 | ### Usage with ReasonML
191 |
192 | While there are no official bindings for using `next-urql` with ReasonML, porting `next-urql` to Reason is not too difficult. Moreover, having your own bindings means you can select only the pieces you need from the library. Here's an example of how you could bind `next-urql` if you only needed access to the non-functional `clientOptions` API, and only wanted to pass a `url` and `fetchOptions`. This assumes BuckleScript 7 to take advantage of records compiling into plain JS objects and assumes usage of [`bs-fetch`](https://github.com/reasonml-community/bs-fetch).
193 |
194 | ```reason
195 | type clientOptions = {
196 | url: string,
197 | fetchOptions: Fetch.requestInit
198 | };
199 |
200 | [@bs.module "next-urql"]
201 | external withUrqlClient:
202 | (. clientOptions) =>
203 | (. React.component('props)) => React.component('props) =
204 | "withUrqlClient";
205 | ```
206 |
207 | Which can then be used like so:
208 |
209 | ```reason
210 | let headers = Fetch.HeadersInit.make({ "Content-Type": "application/json" });
211 | let client = {
212 | url: "https://mygraphqlapi.com/graphql",
213 | fetchOptions: Fetch.RequestInit.make(~headers, ~method_=POST, ())
214 | };
215 |
216 | [@react.component]
217 | let make = () => {
218 |
"Heck yeah, next-urql with Reason!"->React.string
219 | };
220 |
221 | let default = (withUrqlClient(. clientOptions))(. make);
222 | ```
223 |
224 | The great part about writing thin bindings like this is that they are zero cost – in fact, the above bindings get totally compiled away by BuckleScript, so you get the full benefits of type safety with absolutely zero runtime cost!
225 |
226 | ### Examples
227 |
228 | You can see simple example projects using `next-urql` in the `examples` directory or on [CodeSandbox](https://codesandbox.io/s/next-urql-pokedex-oqj3x).
229 |
230 | ### Caveats
231 |
232 | `withUrqlClient` implements Next's unique `getInitialProps` method under the hood. This means that any page containing a component wrapped by `withUrqlClient` will be opted out of [automatic static optimization](https://nextjs.org/docs#automatic-static-optimization). Automatic static optimization was added in Next v9, so you shouldn't worry about this if using an earlier version of Next. This is **not** unique to `next-urql` – any implementation of `getInitialProps` by any component in your application will cause Next to opt out of automatic static optimization.
233 |
--------------------------------------------------------------------------------
/__tests__/init-urql-client.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ssrExchange,
3 | debugExchange,
4 | dedupExchange,
5 | cacheExchange,
6 | fetchExchange,
7 | } from 'urql';
8 |
9 | import { initUrqlClient } from '../src/init-urql-client';
10 | import { SSRData } from 'urql/dist/types/exchanges/ssr';
11 |
12 | describe('initUrqlClient', () => {
13 | it('should return the urqlClient instance and ssrCache', () => {
14 | const [urqlClient, ssrCache] = initUrqlClient({
15 | url: 'http://localhost:3000',
16 | });
17 |
18 | expect(urqlClient).toHaveProperty('url', 'http://localhost:3000');
19 | expect(urqlClient).toHaveProperty('suspense', true);
20 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
21 | expect(ssrCache!.toString()).toEqual(ssrExchange().toString());
22 | });
23 |
24 | it('should accept an optional mergeExchanges function to allow for exchange composition', () => {
25 | const [urqlClient, ssrCache] = initUrqlClient(
26 | {
27 | url: 'http://localhost:3000',
28 | },
29 | ssrEx => [
30 | debugExchange,
31 | dedupExchange,
32 | cacheExchange,
33 | ssrEx,
34 | fetchExchange,
35 | ],
36 | );
37 |
38 | expect(urqlClient).toHaveProperty('url', 'http://localhost:3000');
39 | expect(urqlClient).toHaveProperty('suspense', true);
40 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
41 | expect(ssrCache!.toString()).toEqual(ssrExchange().toString());
42 | });
43 |
44 | it('should accept some initial state to populate the cache', () => {
45 | const initialState: SSRData = {
46 | 123: { data: { name: 'Kadabra', type: 'Psychic' } },
47 | 456: { data: { name: 'Butterfree', type: ['Psychic', 'Bug'] } },
48 | };
49 |
50 | const [urqlClient, ssrCache] = initUrqlClient(
51 | {
52 | url: 'http://localhost:3000',
53 | },
54 | undefined,
55 | initialState,
56 | );
57 |
58 | expect(urqlClient).toHaveProperty('url', 'http://localhost:3000');
59 | expect(urqlClient).toHaveProperty('suspense', true);
60 |
61 | const data = ssrCache && ssrCache.extractData();
62 | expect(data).toEqual(initialState);
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/__tests__/with-urql-client.spec.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow, configure } from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Client, defaultExchanges, composeExchanges } from 'urql';
5 |
6 | import { withUrqlClient, NextUrqlPageContext } from '../src';
7 | import * as init from '../src/init-urql-client';
8 |
9 | const MockApp: React.FC = () => {
10 | return ;
11 | };
12 |
13 | const MockAppTree: React.FC = () => {
14 | return ;
15 | };
16 |
17 | describe('withUrqlClient', () => {
18 | const spyInitUrqlClient = jest.spyOn(init, 'initUrqlClient');
19 | const mockMergeExchanges = jest.fn(() => defaultExchanges);
20 | let Component: any;
21 |
22 | beforeAll(() => {
23 | configure({ adapter: new Adapter() });
24 | });
25 |
26 | afterEach(() => {
27 | spyInitUrqlClient.mockClear();
28 | mockMergeExchanges.mockClear();
29 | });
30 |
31 | describe('with client options', () => {
32 | beforeEach(() => {
33 | Component = withUrqlClient({ url: 'http://localhost:3000' })(MockApp);
34 | });
35 |
36 | const mockContext: NextUrqlPageContext = {
37 | AppTree: MockAppTree,
38 | pathname: '/',
39 | query: {},
40 | asPath: '/',
41 | urqlClient: {} as Client,
42 | };
43 |
44 | it('should create the client instance when the component mounts', () => {
45 | const tree = shallow();
46 | const app = tree.find(MockApp);
47 |
48 | expect(app.props().urqlClient).toBeInstanceOf(Client);
49 | expect(app.props().urqlClient.url).toBe('http://localhost:3000');
50 | expect(spyInitUrqlClient).toHaveBeenCalledTimes(1);
51 | });
52 |
53 | it('should create the urql client instance server-side inside getInitialProps and client-side in the component', async () => {
54 | const props =
55 | Component.getInitialProps &&
56 | (await Component.getInitialProps(mockContext));
57 | expect(spyInitUrqlClient).toHaveBeenCalledTimes(1);
58 |
59 | const tree = shallow();
60 | const app = tree.find(MockApp);
61 |
62 | expect(spyInitUrqlClient).toHaveBeenCalledTimes(2);
63 | expect(app.props().urqlClient).toBeInstanceOf(Client);
64 | expect(app.props().urqlClient.url).toEqual('http://localhost:3000');
65 | });
66 | });
67 |
68 | describe('with ctx callback to create client options', () => {
69 | // Simulate a token that might be passed in a request to the server-rendered application.
70 | const token = Math.random()
71 | .toString(36)
72 | .slice(-10);
73 |
74 | const mockContext: NextUrqlPageContext = {
75 | AppTree: MockAppTree,
76 | pathname: '/',
77 | query: {},
78 | asPath: '/',
79 | req: {
80 | headers: {
81 | cookie: token,
82 | },
83 | } as NextUrqlPageContext['req'],
84 | urqlClient: {} as Client,
85 | };
86 |
87 | beforeEach(() => {
88 | Component = withUrqlClient(
89 | ctx => ({
90 | url: 'http://localhost:3000',
91 | fetchOptions: {
92 | headers: { Authorization: ctx?.req?.headers?.cookie ?? '' },
93 | },
94 | }),
95 | mockMergeExchanges,
96 | )(MockApp);
97 | });
98 |
99 | it('should allow a user to access the ctx object from Next on the server', async () => {
100 | Component.getInitialProps &&
101 | (await Component.getInitialProps(mockContext));
102 | expect(spyInitUrqlClient).toHaveBeenCalledWith(
103 | {
104 | url: 'http://localhost:3000',
105 | fetchOptions: { headers: { Authorization: token } },
106 | },
107 | mockMergeExchanges,
108 | );
109 | });
110 | });
111 |
112 | describe('with mergeExchanges provided', () => {
113 | beforeEach(() => {
114 | Component = withUrqlClient(
115 | { url: 'http://localhost:3000' },
116 | mockMergeExchanges,
117 | )(MockApp);
118 | });
119 |
120 | it('should call the user-supplied mergeExchanges function', () => {
121 | const tree = shallow();
122 | const app = tree.find(MockApp);
123 |
124 | expect(app.props().urqlClient).toBeInstanceOf(Client);
125 | expect(app.props().urqlClient.exchange.toString()).toEqual(
126 | composeExchanges(defaultExchanges).toString(),
127 | );
128 | expect(mockMergeExchanges).toHaveBeenCalledTimes(1);
129 | });
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/assets/next_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FormidableLabs/next-urql/e812598a2261491fdf3612202c2ec1ab0f6f9796/assets/next_logo.png
--------------------------------------------------------------------------------
/assets/urql_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FormidableLabs/next-urql/e812598a2261491fdf3612202c2ec1ab0f6f9796/assets/urql_logo.png
--------------------------------------------------------------------------------
/examples/1-with-urql-client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/examples/1-with-urql-client/README.md:
--------------------------------------------------------------------------------
1 | ## `next-urql` – Page Component Example
2 |
3 | This example is a small reference of how to integrate `next-urql` with your NextJS application by wrapping a Page component. The project is a small Pokedex querying the excellent [Pokemon GraphQL API](https://github.com/lucasbento/graphql-pokemon).
4 |
5 | See this example on [CodeSandbox](https://codesandbox.io/s/next-urql-pokedex-oqj3x).
6 |
7 | ### Installation
8 |
9 | To get the example project running, follow these two steps:
10 |
11 | ```sh
12 | yarn
13 | yarn start
14 | ```
15 |
16 | The example project should spin up at `http://localhost:3000`. `yarn start` will always run the build of the `next-urql` source, so you should see changes picked up once the dev server boots up. However, if you make changes to the `next-urql` source while the dev server is running, you'll need to run `yarn start` again to see those changes take effect.
17 |
--------------------------------------------------------------------------------
/examples/1-with-urql-client/components/pokemon_list.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import gql from 'graphql-tag';
3 | import { useQuery } from 'urql';
4 |
5 | const typesToIcon = new Map([
6 | ['Bug', '/types/Bug@2x.png'],
7 | ['Dark', '/types/Dark@2x.png'],
8 | ['Dragon', '/types/Dragon@2x.png'],
9 | ['Electric', '/types/Electric@2x.png'],
10 | ['Fairy', '/types/Fairy@2x.png'],
11 | ['Fighting', '/types/Fight@2x.png'],
12 | ['Fire', '/types/Fire@2x.png'],
13 | ['Ghost', '/types/Ghost@2x.png'],
14 | ['Grass', '/types/Grass@2x.png'],
15 | ['Ground', '/types/Ground@2x.png'],
16 | ['Ice', '/types/Ice@2x.png'],
17 | ['Normal', '/types/Normal@2x.png'],
18 | ['Poison', '/types/Poison@2x.png'],
19 | ['Psychic', '/types/Psychic@2x.png'],
20 | ['Rock', '/types/Rock@2x.png'],
21 | ['Steel', '/types/Steel@2x.png'],
22 | ['Water', '/types/Water@2x.png'],
23 | ]);
24 |
25 | const queryPokémon = gql`
26 | query pokemon($first: Int!) {
27 | pokemons(first: $first) {
28 | id
29 | name
30 | types
31 | resistant
32 | weaknesses
33 | image
34 | evolutions {
35 | name
36 | }
37 | }
38 | }
39 | `;
40 |
41 | interface PokémonData {
42 | pokemons: Pokémon[];
43 | }
44 |
45 | interface PokémonVariables {
46 | first: number;
47 | }
48 |
49 | interface Pokémon {
50 | id: string;
51 | name: string;
52 | types: string[];
53 | resistant: string[];
54 | weaknesses: string[];
55 | image: string;
56 | evolutions: {
57 | name: string;
58 | };
59 | }
60 |
61 | const PokémonList: React.FC = () => {
62 | const [result] = useQuery({
63 | query: queryPokémon,
64 | variables: { first: 20 },
65 | });
66 |
67 | if (result.fetching || !result.data) {
68 | return null;
69 | }
70 |
71 | if (result.error) {
72 | return null;
73 | }
74 |
75 | return (
76 | <>
77 |