21 | );
22 | };
23 |
24 | Home.getInitialProps = () => {
25 | return {
26 | title: 'Pokédex',
27 | };
28 | };
29 |
30 | export default Home;
31 |
--------------------------------------------------------------------------------
/examples/3-with-custom-exchange/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "next-urql": ["../../"]
6 | },
7 | "target": "es5",
8 | "lib": ["dom", "dom.iterable", "esnext"],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve"
20 | },
21 | "exclude": ["node_modules"],
22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
23 | }
24 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/examples/2-with-_app.js/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | webpack(config) {
5 | config.resolve.alias.react = path.resolve(
6 | __dirname,
7 | '../../node_modules/react/',
8 | );
9 | config.resolve.alias['react-dom'] = path.resolve(
10 | __dirname,
11 | '../../node_modules/react-dom/',
12 | );
13 | config.resolve.alias['react-is'] = path.resolve(
14 | __dirname,
15 | '../../node_modules/react-is/',
16 | );
17 | config.resolve.alias.urql = path.resolve(
18 | __dirname,
19 | '../../node_modules/urql/',
20 | );
21 | config.resolve.alias['next-urql'] = path.resolve(__dirname, '../../');
22 | return config;
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/examples/1-with-urql-client/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | webpack(config) {
5 | config.resolve.alias.react = path.resolve(
6 | __dirname,
7 | '../../node_modules/react/',
8 | );
9 | config.resolve.alias['react-dom'] = path.resolve(
10 | __dirname,
11 | '../../node_modules/react-dom/',
12 | );
13 | config.resolve.alias['react-is'] = path.resolve(
14 | __dirname,
15 | '../../node_modules/react-is/',
16 | );
17 | config.resolve.alias.urql = path.resolve(
18 | __dirname,
19 | '../../node_modules/urql/',
20 | );
21 | config.resolve.alias['next-urql'] = path.resolve(__dirname, '../../');
22 | return config;
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/examples/3-with-custom-exchange/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | webpack(config) {
5 | config.resolve.alias.react = path.resolve(
6 | __dirname,
7 | '../../node_modules/react/',
8 | );
9 | config.resolve.alias['react-dom'] = path.resolve(
10 | __dirname,
11 | '../../node_modules/react-dom/',
12 | );
13 | config.resolve.alias['react-is'] = path.resolve(
14 | __dirname,
15 | '../../node_modules/react-is/',
16 | );
17 | config.resolve.alias.urql = path.resolve(
18 | __dirname,
19 | '../../node_modules/urql/',
20 | );
21 | config.resolve.alias['next-urql'] = path.resolve(__dirname, '../../');
22 | return config;
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/examples/2-with-_app.js/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withUrqlClient, NextUrqlAppContext } from 'next-urql';
3 | import NextApp, { AppProps } from 'next/app';
4 |
5 | const App = ({ Component, pageProps }: AppProps) => {
6 | return ;
7 | };
8 |
9 | // Implement getInitialProps if your Page components call getInitialProps themselves.
10 | // https://nextjs.org/docs/advanced-features/custom-app
11 | App.getInitialProps = async (ctx: NextUrqlAppContext) => {
12 | const appProps = await NextApp.getInitialProps(ctx);
13 |
14 | return {
15 | ...appProps,
16 | };
17 | };
18 |
19 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
20 | // @ts-ignore
21 | export default withUrqlClient({ url: 'https://graphql-pokemon.now.sh' })(App);
22 |
--------------------------------------------------------------------------------
/examples/3-with-custom-exchange/README.md:
--------------------------------------------------------------------------------
1 | ## `next-urql` - Custom Exchange Example
2 |
3 | This example is a small reference of how to implement and use a custom exchange in your urql client with `next-urql`. The project is a small Pokedex querying the excellent [Pokemon GraphQL API](https://github.com/lucasbento/graphql-pokemon).
4 |
5 | ### Installation
6 |
7 | To get the example project running, follow these two steps:
8 |
9 | ```sh
10 | yarn
11 | yarn start
12 | ```
13 |
14 | 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.
15 |
--------------------------------------------------------------------------------
/examples/2-with-_app.js/README.md:
--------------------------------------------------------------------------------
1 | ## `next-urql` - `_app.js` Example
2 |
3 | This example is a small reference of how to integrate `next-urql` with your NextJS application by wrapping the component exported in `_app.js`. The project is a small Pokedex querying the excellent [Pokemon GraphQL API](https://github.com/lucasbento/graphql-pokemon).
4 |
5 | ### Installation
6 |
7 | To get the example project running, follow these two steps:
8 |
9 | ```sh
10 | yarn
11 | yarn start
12 | ```
13 |
14 | 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.
15 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/examples/3-with-custom-exchange/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 | import { withUrqlClient } from 'next-urql';
4 | import { dedupExchange, cacheExchange, fetchExchange } from 'urql';
5 | import { SSRExchange } from 'urql/dist/types/exchanges/ssr';
6 |
7 | import PokémonList from '../components/pokemon_list';
8 | import { urlExchange } from '../utils/url-exchange';
9 |
10 | const Home: React.FC = () => (
11 |
12 |
13 | Home
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default withUrqlClient(
22 | { url: 'https://graphql-pokemon.now.sh' },
23 | (ssrExchange: SSRExchange) => [
24 | dedupExchange,
25 | urlExchange,
26 | cacheExchange,
27 | ssrExchange,
28 | fetchExchange,
29 | ],
30 | )(Home);
31 |
--------------------------------------------------------------------------------
/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/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NextComponentType } from 'next';
3 | import Head from 'next/head';
4 | import { withUrqlClient, NextUrqlPageContext } from 'next-urql';
5 | import PokémonList from '../components/pokemon_list';
6 |
7 | interface InitialProps {
8 | title: string;
9 | }
10 |
11 | const Home: NextComponentType<
12 | NextUrqlPageContext,
13 | InitialProps,
14 | InitialProps
15 | > = ({ title }) => (
16 |
102 |
139 | >
140 | );
141 | };
142 |
143 | export default PokémonList;
144 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/camelcase */
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import babel from 'rollup-plugin-babel';
5 | import typescript from 'rollup-plugin-typescript2';
6 | import { terser } from 'rollup-plugin-terser';
7 | import replace from '@rollup/plugin-replace';
8 | import buble from '@rollup/plugin-buble';
9 |
10 | import pkg from './package.json';
11 |
12 | const extensions = ['.js', '.jsx', '.ts', '.tsx'];
13 |
14 | const external = [];
15 |
16 | if (pkg.peerDependencies) {
17 | external.push(...Object.keys(pkg.peerDependencies));
18 | }
19 |
20 | if (pkg.dependencies) {
21 | external.push(...Object.keys(pkg.dependencies));
22 | }
23 |
24 | const terserPretty = terser({
25 | warnings: true,
26 | ecma: 5,
27 | keep_fnames: true,
28 | compress: {
29 | conditionals: false,
30 | if_return: false,
31 | join_vars: false,
32 | keep_fnames: true,
33 | loops: false,
34 | pure_getters: true,
35 | toplevel: true,
36 | sequences: false,
37 | },
38 | mangle: false,
39 | output: {
40 | braces: true,
41 | indent_level: 2,
42 | },
43 | });
44 |
45 | const terserMinified = terser({
46 | warnings: true,
47 | ecma: 5,
48 | toplevel: true,
49 | compress: {
50 | keep_infinity: true,
51 | passes: 10,
52 | pure_getters: true,
53 | },
54 | });
55 |
56 | const makePlugins = isProduction =>
57 | [
58 | resolve({
59 | mainFields: ['module', 'jsnext', 'main'],
60 | browser: true,
61 | }),
62 | commonjs({
63 | ignoreGlobal: true,
64 | include: /\/node_modules\//,
65 | namedExports: {
66 | // eslint-disable-next-line @typescript-eslint/no-var-requires
67 | react: Object.keys(require('react')),
68 | },
69 | }),
70 | typescript({
71 | typescript: require('typescript'),
72 | cacheRoot: './node_modules/.cache/.rts2_cache',
73 | useTsconfigDeclarationDir: true,
74 | tsconfigDefaults: {
75 | compilerOptions: {
76 | sourceMap: true,
77 | },
78 | },
79 | tsconfigOverride: {
80 | compilerOptions: {
81 | declaration: !isProduction,
82 | declarationDir: './dist/types',
83 | target: 'es6',
84 | },
85 | include: ['src/**/*'],
86 | exclude: ['__tests__/**/*'],
87 | },
88 | }),
89 | buble({
90 | transforms: {
91 | unicodeRegExp: false,
92 | dangerousForOf: true,
93 | dangerousTaggedTemplateString: true,
94 | generator: false,
95 | },
96 | objectAssign: 'Object.assign',
97 | exclude: 'node_modules/**',
98 | }),
99 | babel({
100 | babelrc: false,
101 | extensions,
102 | include: ['src/**/*'],
103 | exclude: 'node_modules/**',
104 | plugins: [
105 | '@babel/plugin-transform-object-assign',
106 | [
107 | 'babel-plugin-transform-async-to-promises',
108 | {
109 | inlineHelpers: true,
110 | externalHelpers: true,
111 | },
112 | ],
113 | ],
114 | }),
115 | isProduction &&
116 | replace({
117 | 'process.env.NODE_ENV': JSON.stringify('production'),
118 | }),
119 | isProduction ? terserMinified : terserPretty,
120 | ].filter(Boolean);
121 |
122 | export default [
123 | {
124 | input: './src/index.ts',
125 | external,
126 | plugins: makePlugins(false),
127 | output: [
128 | {
129 | file: './dist/next-urql.js',
130 | format: 'cjs',
131 | },
132 | {
133 | file: './dist/next-urql.es.js',
134 | format: 'esm',
135 | },
136 | ],
137 | },
138 | {
139 | input: './src/index.ts',
140 | external,
141 | plugins: makePlugins(true),
142 | output: [
143 | {
144 | file: './dist/next-urql.min.js',
145 | format: 'cjs',
146 | },
147 | {
148 | file: './dist/next-urql.es.min.js',
149 | format: 'esm',
150 | },
151 | ],
152 | },
153 | ];
154 |
--------------------------------------------------------------------------------
/__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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------