diff --git a/CHANGELOG.md b/CHANGELOG.md index 4465a5898e..f50f26b20b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,63 @@ # Changelog -## v1.0.0 +All notable changes to this project will be documented in this file. +If a change is missing an attribution, it may have been made by a Core Contributor. + +- Critical bugfixes or breaking changes are marked using a `warning` symbol: ⚠️ +- Significant new features or enhancements are marked using the `sparkles` symbol: ✨ + +_The format is based on [Keep a Changelog](http://keepachangelog.com/)._ + +## [v1.1.0](https://github.com/FormidableLabs/urql/compare/v1.0.5...v1.1.0) + +This release introduces support for **server-side rendering**. +You can find out more about it by reading +[the new Basics section on how to set it up.](https://github.com/FormidableLabs/urql/blob/master/docs/basics.md#server-side-rendering) + +This version now also requires a version of React supporting hooks! (>= 16.8.0) +We unfortunately forgot to correct the `peerDependencies` entries in our v1.0.0 release. + +- ✨ Add **server-side rendering** support (see [#268](https://github.com/FormidableLabs/urql/pull/268)) +- ✨ Ensure that state changes are applied immediately on mount (see [#256](https://github.com/FormidableLabs/urql/pull/256)) +- Ensure that effects are run immediately on mount (see [#250](https://github.com/FormidableLabs/urql/pull/250)) +- ⚠️ Remove `create-react-context` and bump React peer dependency (see [#252](https://github.com/FormidableLabs/urql/pull/252)) +- Add generics to the `Query`, `Mutation`, and `Subscription` components +- ⚠️ Fix issues where `useQuery` wouldn't update or teardown correctly (see [#243](https://github.com/FormidableLabs/urql/pull/243)) +- ✨ Add support for `pause` prop/option to `useQuery` and `Query` (see [#237](https://github.com/FormidableLabs/urql/pull/237)) -> This changelog entry is still incomplete and doesn't quite list all the changed. +## [v1.0.5](https://github.com/FormidableLabs/urql/compare/v1.0.4...v1.0.5) -### Additions +- Export `MutationProps` types for TS typings, by [@mxstbr](https://github.com/mxstbr) (see [#236](https://github.com/FormidableLabs/urql/pull/236)) +- Export `Use*Args` types for TS typings, by [@mxstbr](https://github.com/mxstbr) (see [#235](https://github.com/FormidableLabs/urql/pull/235)) +- Export all hook response types for TS typings, by [@good-idea](https://github.com/good-idea) (see [#233](https://github.com/FormidableLabs/urql/pull/233)) +- ⚠ Fix runtime error in `cachExchange` where already deleted keys where being accessed (see [#223](https://github.com/FormidableLabs/urql/pull/223)) +- ⚠️ Fix `cacheExchange` not forwarding teardowns correctly, which lead to unnecessary/outdated queries being executed, by [@federicobadini](https://github.com/federicobadini) (see [#222](https://github.com/FormidableLabs/urql/pull/222)) +- Change `GraphQLRequest` to always pass on a parsed GraphQL `DocumentNode` instead of just a string, which reduces work (see [#221](https://github.com/FormidableLabs/urql/pull/221)) +- Fix incorrect TS types by using `Omit` (see [#220](https://github.com/FormidableLabs/urql/pull/220)) -- `Client` / `createClient` config object has additional optional value `exchanges`. -- `Connect` child function argument `mutations` is now typed. +## [v1.0.4](https://github.com/FormidableLabs/urql/compare/v1.0.3...v1.0.4) -### Changes +- Fix `__typename` not being extracted from responses correctly, which broke caching +- Fix `fetchOptions` being called in the client instead of the `fetchExchange` +- Improve `CombinedError` to actually extend `Error` and rehydrate `GraphQLError` instances +- Fix `executeMutation` prop not accepting any generics types -- `query` function has been renamed to `createQuery` for clarity. -- `mutation` function has been renamed to `createMutation` for clarity. -- `Client` has been replaced with functional client; created using `createClient`. -- `createClient` returns an object with a function for creating a client instance - see documentation for further information. -- `Connect` component prop `query` now only supports a single query element (multiple GraphQL query strings can be declared in a single Query object). -- `Connect` component prop `mutation` is now named `mutations`. -- `Connect` component prop `subscription` is now named `subscriptions` and is an array of subscriptions. -- `Connect` component prop `updateSubscription` signature slightly changed to the following: `(type, state, data) => newState`. -- `Connect` child function argument now groups mutations into a single `mutations` property. -- `Exchanges` have changed substantially, please see documentation for more information (default exchanges should work as expected). +## [v1.0.3](https://github.com/FormidableLabs/urql/compare/v1.0.2...v1.0.3) + +- Fix bug where `variables` were only compared using reference equality, leading to + infinite rerenders + +## [v1.0.2](https://github.com/FormidableLabs/urql/compare/v1.0.0...v1.0.2) + +- Allow `graphql-tag` / `DocumentNode` usage; Operations' queries can now be `DocumentNode`s +- Generating keys for queries has been optimized + +https://github.com/FormidableLabs/urql/compare/v1.0.4...v1.0.5 + +## v1.0.0 -### Removals +> Since the entire library has been rewritten for v1.0.0, no changes +> are listed here! -- `Client` / `createClient` config object no longer has optional value `initialCache`. -- `Client` / `createClient` config object no longer has optional value `cache` (`Custom Caches` are replaced by the ability to create _Exchanges_). -- `Client` / `createClient` config object no longer has optional value `transformExchange` (transforms should be handled by _Exchanges_). -- `Connect` component prop `shouldInvalidate` is no longer used (use `refetch(true)` or implement custom cache Exchange if needed). -- `Connect` component prop `cache` is no longer used (declare exchanges in `Client` without cacheExchange if caching needs to be disabled). -- `Connect` component prop `typeInvalidation` is no longer used (create custom exchange if typeInvalidation is not desired). -- `Connect` child function argument no longer provides `loaded` boolean value. -- `Connect` child function argument no longer provides `refreshAllFromCache` function. +`urql` v1 is more customisable than ever with "Exchanges", which +allow you to change every aspect of how `urql` works. diff --git a/README.md b/README.md index 8e1c09bd2c..4a6fdec9fb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ -# urql -
+ urql + +
+
+ A highly customisable and versatile GraphQL client for React +

+ Build Status @@ -18,12 +23,11 @@ Maintenance Status +

-Steve Urkel - ## ✨ Features - 📦 **One package** to get a working GraphQL client in React @@ -35,15 +39,17 @@ While GraphQL is an elegant protocol and schema language, client libraries today typically come with large API footprints. We aim to create something more lightweight instead. -## [Documentation](docs/README.md) +## [Documentation](https://formidable.com/open-source/urql/docs) + +[The documentation contains everything you need to know about `urql`](https://formidable.com/open-source/urql/docs) -[The documentation contains everything you need to know about `urql`](docs/README.md) +- [Getting Started guide](https://formidable.com/open-source/urql/docs/getting-started/) +- [Architecture](https://formidable.com/open-source/urql/docs/architecture/) +- [Basics](https://formidable.com/open-source/urql/docs/basics/) +- [Extending & Experimenting](https://formidable.com/open-source/urql/docs/extending-&-experimenting/) +- [API](https://formidable.com/open-source/urql/docs/api/) -- [Getting Started guide](docs/getting-started.md) -- [Architecture](docs/architecture.md) -- [Basics](docs/basics.md) -- [Extending & Experimenting](docs/extending-and-experimenting.md) -- [API](docs/api.md) +_You can find the raw markdown files inside this repository's `docs` folder._ ## Quick Start Guide @@ -72,7 +78,23 @@ ReactDOM.render( ); ``` -This allows you to now use the `` component to fetch data from your server: +This allows you to use the `useQuery` hook in your function component to +fetch data from your server: + +```js +import { useQuery } from 'urql'; + +const YourComponent = () => { + const [result] = useQuery({ + query: `{ todos { id } }`, + }); + + const { fetching, data } = result; + return fetching ? : ; +}; +``` + +Alternatively you can take advantage of the `` component: ```js import { Query } from 'urql'; @@ -84,18 +106,7 @@ import { Query } from 'urql'; ; ``` -Alternatively you can take advantage of the `useQuery` hook in your function component: - -```js -import { useQuery } from 'urql'; - -const YourComponent = () => { - const [{ fetching, data }] = useQuery({ query: `{ todos { id } }` }); - return fetching ? : ; -}; -``` - -[Learn the full API in the "Getting Started" docs!](docs/getting-started.md) +[Learn the full API in the "Getting Started" docs!](https://formidable.com/open-source/urql/docs/getting-started/) ## Examples diff --git a/docs/api.md b/docs/api.md index f06476282c..b00d801217 100644 --- a/docs/api.md +++ b/docs/api.md @@ -150,7 +150,7 @@ interface UseSubscriptionState { | Prop | Type | Description | | -------- | ---------------- | --------------------------------------------------------------- | -| fetching | `boolean` | Whether the `Subscription` is currently ongoing | +| fetching | `boolean` | Whether the `Subscription` is currently ongoing | | data | `?any` | The GraphQL subscription's data | | error | `?CombinedError` | The `CombinedError` containing any errors that might've occured | @@ -167,11 +167,12 @@ be used in combination with the `useContext` hook. The client manages all operations and ongoing requests to the exchange pipeline. It accepts a bunch of inputs when it's created -| Input | Type | Description | -| ------------ | ---------------------------------- | -------------------------------------------------------------------------------------------- | -| url | `string` | The GraphQL API URL as used by `fetchExchange` | -| fetchOptions | `RequestInit \| () => RequestInit` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request | -| exchanges | `Exchange[]` | An array of `Exchange`s that the client should use instead of the list of `defaultExchanges` | +| Input | Type | Description | +| ------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| url | `string` | The GraphQL API URL as used by `fetchExchange` | +| fetchOptions | `RequestInit \| () => RequestInit` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request | +| suspense | `?boolean` | Activates the experimental React suspense mode, which can be used during server-side rendering to prefetch data | +| exchanges | `Exchange[]` | An array of `Exchange`s that the client should use instead of the list of `defaultExchanges` | `urql` also exposes `createClient()` that is just a convenient alternative to calling `new Client()`. @@ -408,6 +409,40 @@ It accepts a single input: `{ forwardSubscription }`. This is a function that receives an enriched operation and must return an Observable-like object that streams `GraphQLResult`s with `data` and `errors`. +### `ssrExchange` (Exchange factory) + +The `ssrExchange` as [described in the Basics section](basics.md#server-side-rendering). +It's of type `Options => Exchange`. + +It accepts a single input, `{ initialState }`, which is completely +optional and populates the server-side rendered data with +a rehydrated cache. + +This can be used to extract data that has been queried on +the server-side, which is also described in the Basics section, +and is also used on the client-side to restore server-side +rendered data. + +When called, this function creates an `Exchange`, which also has +two methods on it: + +- `.restoreData(data)` which can be used to inject data, typically + on the client-side. +- `.extractData()` which is typically used on the server-side to + extract the server-side rendered data. + +Basically, the `ssrExchange` is a small cache that collects data +during the server-side rendering pass, and allows you to populate +the cache on the client-side with the same data. + +During React rehydration this cache will be emptied, +becomes inactive, and won't change the results of queries after +rehydration. + +It needs to be used _after_ other caching Exchanges like the +`cacheExchange`, but before any _asynchronous_ Exchange like +the `fetchExchange`. + ### `debugExchange` (Exchange) An exchange that writes incoming `Operation`s to `console.log` and diff --git a/docs/basics.md b/docs/basics.md index 56469977d8..ac5bf6c4c0 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -157,6 +157,165 @@ other things yourself, if needed. [Read more about customising `urql` in the "Extending & Experimenting" section.](extending-and-experimenting.md) +## Server-side rendering + +Server-side rendering is a common method to reduce the time it takes for +a user to see a React page's content. Typically this is implemented using +the [`react-dom/server` package](https://reactjs.org/docs/react-dom-server.html). + +`urql` can be set up to fetch data on the server and rehydrate this data +on the client, so that the user's browser does not need to refetch it and +can seamlessly rehydrate your React page. + +There are two parts in `urql` that enable server-side rendering: + +- The `Client` has a `suspense` option, which enables support for React's + experimental Suspense API for data fetching, which allows us to prefetch + data before calling `renderToString` or `renderToNodeStream`. +- The `ssrExchange`, which is a small operation cache that works together + with Suspense to save data on the server and rehydrate it on the client. + +Since Suspense is still an experimental API there's no official way to use +it to prefetch data on the server-side. For this reason we have a companion +library, [`react-ssr-prepass`](https://github.com/FormidableLabs/react-ssr-prepass), which can be used to run a "prepass" +that fetches all suspended data it finds in a React element tree. + +### Setting up the `Client` + +When you set up the `Client` for server-side rendering, on the server +you will need to set `suspense` to `true` and on the client to `false`, + +```js +import { Client } from 'urql'; + +const client = new Client({ + suspense: !process.browser, + // ... +}); +``` + +You can often achieve this with `process.browser` in most environments if +you're using a single universal file to create a client on the server +and on the client. + +Next up, the `ssrExchange` needs to be set up. It's a factory, since +it has some methods for extracting and rehydrating data. + +```js +import { + Client, + dedupExchange, + cacheExchange, + fetchExchange, + ssrExchange, +} from 'urql'; + +const ssrCache = ssrExchange(); + +const client = new Client({ + exchanges: [ + dedupExchange, + cacheExchange, + // Put the exchange returned by calling ssrExchange after your cacheExchange, + // but before any asynchronous exchanges like the fetchExchange: + ssrCache, + fetchExchange, + ], + // ... + suspense: !process.browser, +}); +``` + +The exchange returned by `ssrExchange()` should be added after the `cacheExchange` +(or any other custom cache exchange you've defined), and before any +asynchronous exchanges like the `fetchExchange`. + +### Prefetching on the server + +In your request handler on the server-side, you'll have to add some +code for handling suspense. Typically this is done using a "prepass" that +walks your element tree and awaits suspended promises. + +In order to execute suspense on the server, you may install +[`react-ssr-prepass`](https://github.com/FormidableLabs/react-ssr-prepass), which is a partial server-side rendering library, +that can be used to execute a prepass on a React element tree. +It supports React's experimental Suspense API and awaits thrown +promises during the server-side prepass, which we'll use to prefetch +all queries in your React app. + +```sh +# react-is is a peer dependency of react-ssr-prepass +yarn add react-ssr-prepass react-is +# or +npm install --save react-ssr-prepass react-is +``` + +Add `react-ssr-prepass` to your server-side rendering code _before_ +calling `renderToString` or `renderToNodeStream`. This will fetch +all suspended promises, including `urql`'s queries. And after +you can use the `ssrExchange()`'s `extractData` method to +get `urql`'s data: + +```js +import ssrPrepass from 'react-ssr-prepass'; + +const handler = (req, res) => { + // ... + // We assume you've already set up the urql Client and have + // the `ssrCache = ssrExchange()` variable from somewhere + + await ssrPrepass(); + + // Extract the data from urql's SSR cache + const urqlData = ssrCache.extractData(); + + // Then you can run your rendering code for which the ssrCache + // should remain unchanged + const reactHtml = renderToString(); + + // Make sure to send urql's data down to the client somehow + const urqlHtml = ``; + + // And send everything down to the client + // ... +}; +``` + +### Rehydrating on the client + +Now you have server-side rendered the page and sent down data +collected during the render pass. As a next step, you should +rehydrate the data on the client-side. + +This is necessary since the same data needs to be available +during React's rehydration so that the client-side renders the +exact same data and UI state. + +You can either do so when creating `ssrExchange`, by passing it `initialState` +as a parameter or calling `restoreData` on it: + +```js +import { ssrExchange } from 'urql'; + +const ssrCache = ssrExchange({ + initialState: window.URQL_DATA, +}); + +// or: + +ssrCache.restoreData(window.URQL_DATA); + +// Assuming this follows the client setup and is added to the `exchanges` list +// ... +``` + +Your setup may vary depending on whether your client initialization code +is universal (single file that executes on client and server-side, such as Next.js' +`async getInitialProps`), or two separate files for client and server. + +If you're using [next.js](https://nextjs.org/) or need some more details on how to set this +up [have a look at our SSR + next.js example project](https://github.com/FormidableLabs/urql/tree/master/examples/3-ssr-with-nextjs). + ## Subscriptions One feature of `urql` that was not mentioned in the diff --git a/docs/urql-banner.gif b/docs/urql-banner.gif new file mode 100644 index 0000000000..cef293a1e1 Binary files /dev/null and b/docs/urql-banner.gif differ