}
108 | ```
109 |
110 | ```jsx
111 |
112 | {(data, { finishedAt }) => `Last updated ${finishedAt.toISOString()}`}
113 |
114 | ```
115 |
116 | ## `` / ``
117 |
118 | This component renders only when the promise is rejected.
119 |
120 | ### Props
121 |
122 | - `children` `function(error: Error, state: Object): Node | Node` Render function or React Node.
123 | - `state` `object` Async state object \(return value of `useAsync()`\).
124 | - `persist` `boolean` Show old error while loading new data. By default it hides as soon as a new promise starts.
125 |
--------------------------------------------------------------------------------
/docs/api/interfaces.md:
--------------------------------------------------------------------------------
1 | # Interfaces
2 |
3 | React Async provides several ways to use it. The classic interface is through the `` component, which is
4 | backwards compatible to React v16.3. More recent React applications will be using hooks, of which two are provided:
5 | `useAsync` and `useFetch`. Functionally, `` and `useAsync` are equivalent. `useFetch` is a special version of
6 | `useAsync` which is tied to the native `fetch` API.
7 |
8 | React Async accepts a wide range of [configuration options](options.md) and returns a set of [state props](state.md).
9 | The way you use these differs slightly between the `useAsync` and `useFetch` hooks, and the `` component.
10 |
11 | ## `Async` component
12 |
13 | ```jsx
14 | {state => ...}
15 | ```
16 |
17 | - [`options`](options.md) Configuration options
18 | - [`state`](state.md) State object
19 |
20 | > We recommend that you pass the options individually, rather than using JSX [spread attributes]. React Async uses
21 | > [render props] to return its state back to you, so it can be used by other components further down the tree.
22 |
23 | [spread attributes]: https://reactjs.org/docs/jsx-in-depth.html#spread-attributes
24 | [render props]: https://reactjs.org/docs/render-props.html
25 |
26 | ## `useAsync` hook
27 |
28 | ```js
29 | const state = useAsync(options)
30 | ```
31 |
32 | - [`state`](state.md) State object
33 | - [`options`](options.md) Configuration options
34 |
35 | > We recommend that you pass `options` as an inline object literal, and that you [destructure] the `state` object to
36 | > extract the properties you need, unless you have multiple instances in the same component.
37 |
38 | [destructure]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
39 |
40 | ## `useFetch` hook
41 |
42 | ```js
43 | const state = useFetch(resource, init, options)
44 | ```
45 |
46 | - [`state`](state.md) State object
47 | - [`resource`][fetch api] The resource you want to fetch
48 | - [`init`][fetch api] Custom request options
49 | - [`options`](options.md) Configuration options
50 |
51 | [fetch api]: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax
52 |
53 | ## `createInstance`
54 |
55 | Besides using the `Async` component directly, you can also create your own instance of it. This allows you to preload it
56 | with options, e.g. to enable global error handling.
57 |
58 | ```js
59 | const CustomAsync = createInstance(defaultOptions, displayName)
60 | ```
61 |
62 | - [`defaultOptions`](options.md) Default configuration options
63 | - `displayName` Name for this instance, used by React DevTools
64 |
--------------------------------------------------------------------------------
/docs/api/options.md:
--------------------------------------------------------------------------------
1 | # Configuration options
2 |
3 | These can be passed in an object to `useAsync(options)`, or as props to `` and custom instances.
4 |
5 | - [`promise`](#promise) An already started Promise instance.
6 | - [`promiseFn`](#promisefn) Function that returns a Promise, automatically invoked.
7 | - [`deferFn`](#deferfn) Function that returns a Promise, manually invoked with `run`.
8 | - [`watch`](#watch) Watch a value and automatically reload when it changes.
9 | - [`watchFn`](#watchfn) Watch this function and automatically reload when it returns truthy.
10 | - [`initialValue`](#initialvalue) Provide initial data or error for server-side rendering.
11 | - [`onResolve`](#onresolve) Callback invoked when Promise resolves.
12 | - [`onReject`](#onreject) Callback invoked when Promise rejects.
13 | - [`onCancel`](#oncancel) Callback invoked when a Promise is cancelled.
14 | - [`reducer`](#reducer) State reducer to control internal state updates.
15 | - [`dispatcher`](#dispatcher) Action dispatcher to control internal action dispatching.
16 | - [`debugLabel`](#debuglabel) Unique label used in DevTools.
17 | - [`suspense`](#suspense) Enable **experimental** Suspense integration.
18 |
19 | `useFetch` additionally takes these options:
20 |
21 | - [`defer`](#defer) Force the use of `deferFn` or `promiseFn`.
22 | - [`json`](#json) Enable JSON parsing of the response.
23 |
24 | ## `promise`
25 |
26 | > `Promise`
27 |
28 | A Promise instance which has already started. It will simply add the necessary resolve/reject callbacks and set `startedAt` to the time `promise` was first provided. Changing the value of `promise` will cancel any pending promise and listen to the new one. If `promise` is initially undefined, the React Async state will be `pending`.
29 |
30 | > Note that `reload` will not do anything when using `promise`. Use `promiseFn` instead.
31 |
32 | ## `promiseFn`
33 |
34 | > `function(props: Object, controller: AbortController): Promise`
35 |
36 | A function that returns a promise. It is automatically invoked in `componentDidMount` and `componentDidUpdate`. The function receives all component props \(or options\) and an AbortController instance as arguments.
37 |
38 | > Be aware that updating `promiseFn` will trigger it to cancel any pending promise and load the new promise. Passing an inline (arrow) function will cause it to change and reload on every render of the parent component. You can avoid this by defining the `promiseFn` value **outside** of the render method. If you need to pass variables to the `promiseFn`, pass them as additional props to ``, as `promiseFn` will be invoked with these props. Alternatively you can use `useCallback` or [memoize-one](https://github.com/alexreardon/memoize-one) to avoid unnecessary updates.
39 |
40 | ## `deferFn`
41 |
42 | > `function(args: any[], props: Object, controller: AbortController): Promise`
43 |
44 | A function that returns a promise. This is invoked only by manually calling `run(...args)`. Any arguments to `run` are passed-through as an array via `args`, so you can pass data through either `args` or `props`, as needed. The `deferFn` is commonly used to send data to the server following a user action, such as submitting a form. You can use this in conjunction with `promiseFn` to fill the form with existing data, then updating it on submit with `deferFn`.
45 |
46 | > Be aware that when using both `promiseFn` and `deferFn`, the shape of their fulfilled value should match, because they both update the same `data`.
47 |
48 | ## `watch`
49 |
50 | > `any`
51 |
52 | Watches this property through `componentDidUpdate` and re-runs the `promiseFn` when the value changes, using a simple reference check \(`oldValue !== newValue`\). If you need a more complex update check, use `watchFn` instead.
53 |
54 | ## `watchFn`
55 |
56 | > `function(props: Object, prevProps: Object): boolean | any`
57 |
58 | Re-runs the `promiseFn` when this callback returns truthy \(called on every update\). Any default props specified by `createInstance` are available too.
59 |
60 | ## `initialValue`
61 |
62 | > `any | Error`
63 |
64 | Initial state for `data` or `error` \(if instance of Error\); useful for server-side rendering. When an `initialValue` is provided, the `promiseFn` will not be invoked on first render. Instead, `status` will be immediately set to `fulfilled` or `rejected` and your components will render accordingly. If you want to trigger the `promiseFn` regardless, you can call `reload()` or use the `watch` or `watchFn` option.
65 |
66 | > Note that `onResolve` or `onReject` is not invoked in this case and no `promise` prop will be created.
67 |
68 | ## `onResolve`
69 |
70 | > `function(data: any): void`
71 |
72 | Callback function invoked when a promise resolves, receives data as argument.
73 |
74 | ## `onReject`
75 |
76 | > `function(reason: Error): void`
77 |
78 | Callback function invoked when a promise rejects, receives rejection reason \(error\) as argument.
79 |
80 | ## `onCancel`
81 |
82 | > `function(): void`
83 |
84 | Callback function invoked when a promise is cancelled, either manually using `cancel()` or automatically due to props changes or unmounting.
85 |
86 | ## `reducer`
87 |
88 | > `function(state: any, action: Object, internalReducer: function(state: any, action: Object))`
89 |
90 | State reducer to take full control over state updates by wrapping the [internal reducer](https://github.com/async-library/react-async/blob/master/packages/react-async/src/reducer.ts). It receives the current state, the dispatched action and the internal reducer. You probably want to invoke the internal reducer at some point.
91 |
92 | > This is a power feature which loosely follows the [state reducer pattern](https://kentcdodds.com/blog/the-state-reducer-pattern). It allows you to control state changes by intercepting actions before they are handled, or by overriding or enhancing the reducer itself.
93 |
94 | ## `dispatcher`
95 |
96 | > `function(action: Object, internalDispatch: function(action: Object), props: Object)`
97 |
98 | Action dispatcher to take full control over action dispatching by wrapping the internal dispatcher. It receives the original action, the internal dispatcher and all component props \(or options\). You probably want to invoke the internal dispatcher at some point.
99 |
100 | > This is a power feature similar to the [state reducer pattern](https://kentcdodds.com/blog/the-state-reducer-pattern). It allows you to control state changes by intercepting actions before they are dispatched, to dispatch additional actions, possibly later in time.
101 |
102 | ## `debugLabel`
103 |
104 | > `string`
105 |
106 | A unique label to describe this React Async instance, used in React DevTools \(through `useDebugValue`\) and React Async DevTools.
107 |
108 | ## `suspense`
109 |
110 | > `boolean`
111 |
112 | Enables **experimental** Suspense integration. This will make React Async throw a promise while loading, so you can use Suspense to render a fallback UI, instead of using ``. Suspense differs in 2 main ways:
113 |
114 | - `` should be an ancestor of your Async component, instead of a descendant. It can be anywhere up in the
115 |
116 | component hierarchy.
117 |
118 | - You can have a single `` wrap multiple Async components, in which case it will render the fallback UI until
119 |
120 | all promises are settled.
121 |
122 | > Note that the way Suspense is integrated right now may change. Until Suspense for data fetching is officially released, we may make breaking changes to its integration in React Async in a minor or patch release. Among other things, we'll probably add a cache of sorts.
123 |
124 | ## `defer`
125 |
126 | > `boolean`
127 |
128 | Enables the use of `deferFn` if `true`, or enables the use of `promiseFn` if `false`. By default this is automatically chosen based on the request method \(`deferFn` for POST / PUT / PATCH / DELETE, `promiseFn` otherwise\).
129 |
130 | ## `json`
131 |
132 | > `boolean`
133 |
134 | Enables or disables JSON parsing of the response body. By default this is automatically enabled if the `Accept` header is set to `"application/json"`.
135 |
--------------------------------------------------------------------------------
/docs/api/state.md:
--------------------------------------------------------------------------------
1 | # State properties
2 |
3 | These are returned in an object by `useAsync()` or provided by `` as render props to the `children` function:
4 |
5 | - [`data`](#data) Last resolved promise value, maintained when new error arrives.
6 | - [`error`](#error) Rejected promise reason, cleared when new data arrives.
7 | - [`value`](#value) The value of `data` or `error`, whichever was last updated.
8 | - [`initialValue`](#initialvalue) The data or error that was provided through the `initialValue` prop.
9 | - [`startedAt`](#startedat) When the current/last promise was started.
10 | - [`finishedAt`](#finishedat) When the last promise was fulfilled or rejected.
11 | - [`status`](#status) One of: `initial`, `pending`, `fulfilled`, `rejected`.
12 | - [`isInitial`](#isinitial) true when no promise has ever started, or one started but was cancelled.
13 | - [`isPending`](#ispending) true when a promise is currently awaiting settlement. Alias: `isLoading`
14 | - [`isFulfilled`](#isfulfilled) true when the last promise was fulfilled. Alias: `isResolved`
15 | - [`isRejected`](#isrejected) true when the last promise was rejected.
16 | - [`isSettled`](#issettled) true when the last promise was fulfilled or rejected \(not initial or pending\).
17 | - [`counter`](#counter) The number of times a promise was started.
18 | - [`promise`](#promise) A reference to the internal wrapper promise, which can be chained on.
19 | - [`run`](#run) Invokes the `deferFn`.
20 | - [`reload`](#reload) Re-runs the promise when invoked, using any previous arguments.
21 | - [`cancel`](#cancel) Cancel any pending promise.
22 | - [`setData`](#setdata) Sets `data` to the passed value, unsets `error` and cancels any pending promise.
23 | - [`setError`](#seterror) Sets `error` to the passed value and cancels any pending promise.
24 |
25 | ## `data`
26 |
27 | > `any`
28 |
29 | Last resolved promise value, maintained when new error arrives.
30 |
31 | ## `error`
32 |
33 | > `Error`
34 |
35 | Rejected promise reason, cleared when new data arrives.
36 |
37 | ## `value`
38 |
39 | > `any | Error`
40 |
41 | The data or error that was last provided \(either through `initialValue` or by settling a promise\).
42 |
43 | ## `initialValue`
44 |
45 | > `any | Error`
46 |
47 | The data or error that was originally provided through the `initialValue` prop.
48 |
49 | ## `startedAt`
50 |
51 | > `Date`
52 |
53 | Tracks when the current/last promise was started.
54 |
55 | ## `finishedAt`
56 |
57 | > `Date`
58 |
59 | Tracks when the last promise was resolved or rejected.
60 |
61 | ## `status`
62 |
63 | > `string`
64 |
65 | One of: `initial`, `pending`, `fulfilled`, `rejected`. These are available for import as `statusTypes`.
66 |
67 | ## `isInitial`
68 |
69 | > `boolean`
70 |
71 | `true` while no promise has started yet, or one was started but cancelled.
72 |
73 | ## `isPending`
74 |
75 | > `boolean`
76 |
77 | `true` while a promise is pending \(loading\), `false` otherwise.
78 |
79 | Alias: `isLoading`
80 |
81 | ## `isFulfilled`
82 |
83 | > `boolean`
84 |
85 | `true` when the last promise was fulfilled \(resolved to a value\).
86 |
87 | Alias: `isResolved`
88 |
89 | ## `isRejected`
90 |
91 | > `boolean`
92 |
93 | `true` when the last promise was rejected.
94 |
95 | ## `isSettled`
96 |
97 | > `boolean`
98 |
99 | `true` when the last promise was either fulfilled or rejected \(i.e. not initial or pending\)
100 |
101 | ## `counter`
102 |
103 | > `number`
104 |
105 | The number of times a promise was started.
106 |
107 | ## `promise`
108 |
109 | > `Promise`
110 |
111 | A reference to the internal wrapper promise created when starting a new promise \(either automatically or by invoking
112 | `run` / `reload`\). It fulfills or rejects along with the provided `promise` / `promiseFn` / `deferFn`. Useful as a
113 | chainable alternative to the `onResolve` / `onReject` callbacks.
114 |
115 | Warning! If you chain on `promise`, you MUST provide a rejection handler \(e.g. `.catch(...)`\). Otherwise React will
116 | throw an exception and crash if the promise rejects.
117 |
118 | ## `run`
119 |
120 | > `function(...args: any[]): void`
121 |
122 | Runs the `deferFn`, passing any arguments provided as an array.
123 |
124 | When used with `useFetch`, `run` has several overloaded signatures:
125 |
126 | > `function(override: OverrideParams | (params: OverrideParams) => OverrideParams): void`
127 | >
128 | > `function(event: SyntheticEvent | Event): void`
129 | >
130 | > `function(): void`
131 |
132 | Where `type OverrideParams = { resource?: RequestInfo } & Partial`.
133 |
134 | This way you can run the `fetch` request with custom `resource` and `init`. If `override` is an object it will be spread
135 | over the default `resource` and `init` for `fetch`. If it's a function it will be invoked with the params defined with
136 | `useFetch`, and should return an `override` object. This way you can either extend or override the value of `resource`
137 | and `init`, for example to change the URL or set custom request headers.
138 |
139 | ## `reload`
140 |
141 | > `function(): void`
142 |
143 | Re-runs the promise when invoked, using the previous arguments.
144 |
145 | ## `cancel`
146 |
147 | > `function(): void`
148 |
149 | Cancels the currently pending promise by ignoring its result and calls `abort()` on the AbortController.
150 |
151 | ## `setData`
152 |
153 | > `function(data: any, callback?: () => void): any`
154 |
155 | Function that sets `data` to the passed value, unsets `error` and cancels any pending promise. Takes an optional callback which is invoked after the state update is completed. Returns the data to enable chaining.
156 |
157 | ## `setError`
158 |
159 | > `function(error: Error, callback?: () => void): Error`
160 |
161 | Function that sets `error` to the passed value and cancels any pending promise. Takes an optional callback which is invoked after the state update is completed. Returns the error to enable chaining.
162 |
--------------------------------------------------------------------------------
/docs/contributing/development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | React Async is a library without visual parts. Only the DevTools have a user interface you can spin up in a browser.
4 | Therefore the development workflow for the core library might be different from what you're used to. Generally, we use a
5 | TDD approach:
6 |
7 | - Write a unit test for the new feature or bug you want to fix. Sometimes you can just extend an existing test.
8 | - Fix the test by implementing the feature or bugfix. Now all tests should pass.
9 | - Optionally refactor the code for performance, readability and style. Probably this will come up during PR review.
10 |
11 | We use the GitHub pull request workflow. In practice this means your workflow looks like this:
12 |
13 | - Fork the repo (or pull the latest upstream) under your own account.
14 | - Make your changes, commit and push them. We don't enforce any commit message format.
15 | - Open a pull request on the main repository against the `next` branch. Make sure to follow the template.
16 | - We'll review your PR and will probably ask for some changes.
17 | - Once ready, we'll merge your PR.
18 | - Your changes will be in the next release.
19 |
20 | ## Working with Storybook
21 |
22 | We use Storybook as a development environment for the DevTools. Spin it up using:
23 |
24 | ```sh
25 | yarn start:storybook
26 | ```
27 |
28 | This should open up Storybook in a browser at http://localhost:6006/
29 | Run it side-by-side with `yarn test --watch` during development. See [Testing](#testing).
30 |
31 | ## Working with the examples
32 |
33 | In the `examples` folder, you will find sample React applications that use React Async in various ways with various other libraries. Please add a new example when introducing a major new feature. Make sure to add it to `now.json` so it is automatically deployed when merged to `master`.
34 |
35 | To run sample examples on your local environments
36 |
37 | ```sh
38 | yarn build:examples
39 | yarn test:examples
40 | yarn start:examples
41 | ```
42 |
43 | ## Resolving issues
44 |
45 | Sometimes your dependencies might end up in a weird state, causing random issues, especially when working with the examples. In this case it often helps to run `yarn clean -y && yarn bootstrap`. This will delete `node_modules` from all packages/examples and do a clean install.
46 |
--------------------------------------------------------------------------------
/docs/contributing/introduction.md:
--------------------------------------------------------------------------------
1 | # Contributing to React Async
2 |
3 | Thanks for your interest in improving React Async! Contributions of any kind are welcome. Please refer to this guide before opening an issue or pull request.
4 |
5 | This repo relies on Yarn workspaces, so you should [install](https://yarnpkg.com/en/docs/install) and use `yarn@1.3.2` or higher as the package manager for this project.
6 |
7 | ## Development guide
8 |
9 | Please have the **_latest_** stable versions of the following on your machine
10 |
11 | - node
12 | - yarn
13 |
14 | ### Initial setup
15 |
16 | To start working on React Async, clone the repo and bootstrap the project:
17 |
18 | ```sh
19 | git clone https://github.com/async-library/react-async.git
20 | cd react-async
21 | yarn && yarn bootstrap && yarn test
22 | ```
23 |
24 | Note that all work is done against the `next` branch, we only merge to `master` when doing a release.
25 |
26 | ### Working with Storybook
27 |
28 | We use Storybook as a development environment, particularly for the DevTools. Spin it up using:
29 |
30 | ```sh
31 | yarn start:storybook
32 | ```
33 |
34 | This should open up Storybook in a browser at http://localhost:6006/
35 | Run it side-by-side with `yarn test --watch` during development. See [Testing](#testing).
36 |
37 | ### Linting
38 |
39 | Use `yarn lint` to verify your code style before committing. It's highly recommended to install the Prettier and ESLint plugins for your IDE. Travis CI will fail your build on lint errors. Configure VS Code with the following settings:
40 |
41 | ```plaintext
42 | "eslint.autoFixOnSave": true,
43 | "eslint.packageManager": "yarn",
44 | "eslint.options": {
45 | "cache": true,
46 | "cacheLocation": ".cache/eslint",
47 | "extensions": [".js", ".jsx", ".mjs", ".json", ".ts", ".tsx"]
48 | },
49 | "eslint.validate": [
50 | "javascript",
51 | "javascriptreact",
52 | {"language": "typescript", "autoFix": true },
53 | {"language": "typescriptreact", "autoFix": true }
54 | ],
55 | "eslint.alwaysShowStatus": true
56 | ```
57 |
58 | This should enable auto-fix for all source files, and give linting warnings and errors within your editor.
59 |
60 | ### Testing
61 |
62 | Use the following command to test all packages in watch mode. Refer to the [Jest CLI options](https://jestjs.io/docs/en/cli#options) for details.
63 |
64 | ```sh
65 | yarn test:watch
66 | ```
67 |
68 | In general, this is sufficient during development. Travis CI will apply a more rigorous set of tests.
69 |
70 | #### Testing for compatibility
71 |
72 | ```sh
73 | yarn test:compat
74 | ```
75 |
76 | This runs all tests using various versions of `react` and `react-dom`, to check for compatibility with older/newer versions of React. This is what CircleCI and Travis run.
77 |
78 | ### Working with the examples
79 |
80 | In the `examples` folder, you will find sample React applications that use React Async in various ways with various other libraries. Please add a new example when introducing a major new feature. Make sure to add it to `now.json` so it is automatically deployed when merged to `master`.
81 |
82 | To run sample examples on your local environments
83 |
84 | ```sh
85 | yarn build:examples
86 | yarn test:examples
87 | yarn start:examples
88 | ```
89 |
90 | ### Resolving issues
91 |
92 | Sometimes your dependencies might end up in a weird state, causing random issues, especially when working with the examples. In this case it often helps to run `yarn clean -y && yarn bootstrap`. This will delete `node_modules` from all packages/examples and do a clean install.
93 |
--------------------------------------------------------------------------------
/docs/contributing/releasing.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | All ongoing development is done on the `next` branch. When preparing for a release, we'll create a `release` branch
4 | which will eventually be merged into `master`. This way, what's on `master` is always what's published on `npm`.
5 |
6 | Release management is currently a manual process, to be performed by core team members only. Here's the process:
7 |
8 | 1. Create a `release` branch, usually based on `next`.
9 | 2. Open a pull request for `release` -> `master`
10 | 3. Write the release notes in the PR description.
11 | 4. Decide on the version number, taking care to follow semver. Do a pre-release before doing the actual release.
12 | 5. Run `yarn bump` to increment the version number in all `package.json` files as well as `lerna.json`.
13 | 6. Commit the version change as "Release vX.X.X" (using the correct version number).
14 | 7. Tag the release commit with `git tag vX.X.X` (using the correct version number).
15 | 8. Push the release commit AND tag: `git push --follow-tags`
16 | 9. Publish each package (in `./packages`) to npm using the script below.
17 | 10. Create a new release on GitHub and copy the release notes there.
18 |
19 | ```
20 | yarn build:packages
21 | cd packages/react-async
22 | npm publish pkg
23 | cd ../react-async-devtools
24 | npm publish pkg
25 | ```
26 |
27 | Take care to publish the `pkg` directory!
28 |
--------------------------------------------------------------------------------
/docs/contributing/setting-up.md:
--------------------------------------------------------------------------------
1 | # Setting up your development environment
2 |
3 | ## Prerequisites
4 |
5 | In order to develop React Async on your local machine, you'll need `git`, `node` and `yarn`.
6 |
7 | ### Git
8 |
9 | To clone the repository, commit your changes and push them upstream, you'll need to have `git` [installed][install git].
10 |
11 | [install git]: https://www.atlassian.com/git/tutorials/install-git
12 |
13 | ### Node.js
14 |
15 | As a JavaScript project, we rely heavily on Node.js. It's recommended to use a version manager such as [fnm] for Mac /
16 | Linux or [nvm-windows] for Windows to install the latest Node.js with.
17 |
18 | [fnm]: https://github.com/Schniz/fnm
19 | [nvm-windows]: https://github.com/coreybutler/nvm-windows
20 |
21 | ### Yarn
22 |
23 | This repo relies on Yarn workspaces, so you should [install][install yarn] and use `yarn@1.3.2` or higher as the package
24 | manager for this project.
25 |
26 | [install yarn]: https://yarnpkg.com/en/docs/install
27 |
28 | ## Project setup
29 |
30 | To start working on React Async, clone the repository and bootstrap the project by running the following commands
31 | one-by-one:
32 |
33 | ```sh
34 | git clone https://github.com/async-library/react-async.git
35 | cd react-async
36 | yarn install
37 | yarn bootstrap
38 | yarn test
39 | ```
40 |
41 | This should install all dependencies, build and link the react-async and react-async-devtools packages to the examples,
42 | and finally run the unit tests. In the end it should succeed with a message (numbers may change):
43 |
44 | ```
45 | Test Suites: 6 passed, 6 total
46 | Tests: 136 passed, 136 total
47 | ```
48 |
49 | > Note that all work is done against the `next` branch, we only merge to `master` when doing a release.
50 |
51 | ## Editor setup
52 |
53 | We recommend using [Visual Studio Code](https://code.visualstudio.com/) with the following extensions:
54 |
55 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
56 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
57 | - [DeepScan](https://marketplace.visualstudio.com/items?itemName=DeepScan.vscode-deepscan)
58 | - [Oceanic Plus](https://marketplace.visualstudio.com/items?itemName=marcoms.oceanic-plus)
59 |
60 | Make sure to enable `editor.formatOnSave`, so Prettier will automatically apply the right code style. For the full
61 | immersive experience you can also install and use the [Overpass Mono](https://overpassfont.org/) font.
62 |
--------------------------------------------------------------------------------
/docs/contributing/testing.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | Use the following command to test all packages in watch mode. Refer to the [Jest CLI options][jest options] for details.
4 |
5 | [jest options]: https://jestjs.io/docs/en/cli#options
6 |
7 | ```sh
8 | yarn test:watch
9 | ```
10 |
11 | In general, this is sufficient during development. CircleCI and Travis will eventually apply a more rigorous set of
12 | tests against your pull request, including the ones below.
13 |
14 | ## Testing the examples
15 |
16 | Because React Async is only a piece in a bigger puzzle, testing for integration with other libraries is very important.
17 | You can run the tests for all examples against your local changes with the following command:
18 |
19 | ```sh
20 | yarn test:examples
21 | ```
22 |
23 | If you want to add integration tests for compatibility with another library, please add an example for it.
24 |
25 | ## Testing for compatibility
26 |
27 | ```sh
28 | yarn test:compat
29 | ```
30 |
31 | This runs all tests using various versions of `react` and `react-dom`, to check for compatibility with older/newer
32 | versions of React. This is what CircleCI and Travis run.
33 |
34 | ## Linting
35 |
36 | Use `yarn lint` to verify your code style before committing. It's highly recommended to install the Prettier and ESLint plugins for your IDE. CircleCI and Travis will fail your build on lint errors.
37 |
--------------------------------------------------------------------------------
/docs/getting-started/devtools.md:
--------------------------------------------------------------------------------
1 | # DevTools
2 |
3 | React Async comes with a separate DevTools package which helps you Debug and develop your asynchronous application
4 | states. You can install it from npm:
5 |
6 | ```text
7 | npm install --save react-async-devtools
8 | ```
9 |
10 | Or if you're using Yarn:
11 |
12 | ```text
13 | yarn add react-async-devtools
14 | ```
15 |
16 | Then simply import it and render the`` component at the root of your app:
17 |
18 | ```jsx
19 | import DevTools from "react-async-devtools"
20 |
21 | export const Root = () => (
22 | <>
23 |
24 |
25 | >
26 | )
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/getting-started/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | You can install `react-async` from npm:
4 |
5 | ```text
6 | npm install --save react-async
7 | ```
8 |
9 | Or if you're using Yarn:
10 |
11 | ```text
12 | yarn add react-async
13 | ```
14 |
15 | > This package requires `react` as a peer dependency. Please make sure to install that as well. If you want to use the
16 | > `useAsync` hook, you'll need `react@16.8.0` or later.
17 |
18 | ## Transpiling for legacy browsers
19 |
20 | This project targets the latest ECMAScript version. Our packages on npm do not contain ES5 code for legacy browsers. If you need to target a browser which does not support the latest version of ECMAScript, you'll have to handle transpilation yourself. Usually this will automatically be handled by the framework you use (CRA, Next.js, Gatsby), but sometimes you may need to tweak your Webpack settings to transpile `react-async` with Babel.
21 |
22 | To transpile `node_modules` with Babel you need to use a `babel.config.js`, for more information see [Babel's documentation](https://babeljs.io/docs/en/configuration#whats-your-use-case).
23 |
24 | In your `webpack.config.js` make sure that the rule for `babel-loader`:
25 |
26 | - doesn't exclude `node_modules` from matching via the `exclude` pattern;
27 | - excludes `core-js` as it shouldn't be transpiled;
28 | - is passed the `configFile` option pointing to the `babel.config.js` file.
29 |
30 | ```
31 | {
32 | test: /\.(js|jsx)$/,
33 | exclude: /\/node_modules\/core-js\//,
34 | use: [{
35 | loader: 'babel-loader',
36 | options: {
37 | configFile: './babel.config.js',
38 | // Caching is recommended when transpiling node_modules to speed up consecutive builds
39 | cacheDirectory: true,
40 | }
41 | }]
42 | }
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/getting-started/upgrading.md:
--------------------------------------------------------------------------------
1 | # Upgrading
2 |
3 | ## Upgrade to v9
4 |
5 | The rejection value for failed requests with `useFetch` was changed. Previously it was the Response object. Now it's an
6 | Error object with `response` property. If you are using `useFetch` and are using the `error` value, expecting it to be
7 | of type Response, you must now use `error.response` instead.
8 |
9 | ## Upgrade to v8
10 |
11 | All standalone helper components were renamed to avoid import naming collision.
12 |
13 | - `` was renamed to ``.
14 | - `` was renamed to ``.
15 | - `` was renamed to ``.
16 | - `` was renamed to `` was renamed to ``.
18 |
19 | > A [codemod](https://github.com/async-library/react-async/tree/master/codemods) is available to automate the upgrade.
20 |
21 | The return type for `run` was changed from `Promise` to `undefined`. You should now use the `promise` prop instead. This
22 | is a manual upgrade. See [`promise`](state.md#promise) for details.
23 |
24 | ## Upgrade to v6
25 |
26 | - `` was renamed to ``.
27 | - Some of the other helpers were also renamed, but the old ones remain as alias.
28 | - Don't forget to deal with any custom instances of `` when upgrading.
29 |
30 | > A [codemod](https://github.com/async-library/react-async/tree/master/codemods) is available to automate the upgrade.
31 |
32 | ## Upgrade to v4
33 |
34 | - `deferFn` now receives an `args` array as the first argument, instead of arguments to `run` being spread at the front
35 | of the arguments list. This enables better interop with TypeScript. You can use destructuring to keep using your
36 | existing variables.
37 |
38 | - The shorthand version of `useAsync` now takes the `options` object as optional second argument. This used to be
39 | `initialValue`, but was undocumented and inflexible.
40 |
--------------------------------------------------------------------------------
/docs/getting-started/usage.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | React Async offers three primary APIs: the `useAsync` hook, the `` component and the `createInstance` factory function. Each has its unique benefits and downsides.
4 |
5 | ## As a hook
6 |
7 | The `useAsync` hook \(available [from React v16.8.0](https://reactjs.org/hooks)\) offers direct access to React Async's core functionality from within your own function components:
8 |
9 | ```jsx
10 | import { useAsync } from "react-async"
11 |
12 | // You can use async/await or any function that returns a Promise
13 | const loadPlayer = async ({ playerId }, { signal }) => {
14 | const res = await fetch(`/api/players/${playerId}`, { signal })
15 | if (!res.ok) throw new Error(res.statusText)
16 | return res.json()
17 | }
18 |
19 | const MyComponent = () => {
20 | const { data, error, isPending } = useAsync({ promiseFn: loadPlayer, playerId: 1 })
21 | if (isPending) return "Loading..."
22 | if (error) return `Something went wrong: ${error.message}`
23 | if (data)
24 | return (
25 |
26 | Player data:
27 |
{JSON.stringify(data, null, 2)}
28 |
29 | )
30 | return null
31 | }
32 | ```
33 |
34 | > Using [helper components](usage.md#with-helper-components) can greatly improve readability of your render functions by not having to write all those conditional returns.
35 |
36 | Or using the shorthand version:
37 |
38 | ```jsx
39 | const MyComponent = () => {
40 | const { data, error, isPending } = useAsync(loadPlayer, options)
41 | // ...
42 | }
43 | ```
44 |
45 | ### With `useFetch`
46 |
47 | Because fetch is so commonly used with `useAsync`, there's a dedicated `useFetch` hook for it:
48 |
49 | ```jsx
50 | import { useFetch } from "react-async"
51 |
52 | const MyComponent = () => {
53 | const headers = { Accept: "application/json" }
54 | const { data, error, isPending, run } = useFetch("/api/example", { headers }, options)
55 | // This will setup a promiseFn with a fetch request and JSON deserialization.
56 |
57 | // you can later call `run` with an optional callback argument to
58 | // last-minute modify the `init` parameter that is passed to `fetch`
59 | function clickHandler() {
60 | run(init => ({
61 | ...init,
62 | headers: {
63 | ...init.headers,
64 | authentication: "...",
65 | },
66 | }))
67 | }
68 |
69 | // alternatively, you can also just use an object that will be spread over `init`.
70 | // please note that this is not deep-merged, so you might override properties present in the
71 | // original `init` parameter
72 | function clickHandler2() {
73 | run({ body: JSON.stringify(formValues) })
74 | }
75 | }
76 | ```
77 |
78 | `useFetch` takes the same arguments as [fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) itself, as well as `options` to the underlying `useAsync` hook. The `options` object takes two special boolean properties: `defer` and `json`. These can be used to switch between `deferFn` and `promiseFn`, and enable JSON parsing. By default `useFetch` automatically uses `promiseFn` or `deferFn` based on the request method \(`deferFn` for POST / PUT / PATCH / DELETE\) and handles JSON parsing if the `Accept` header is set to `"application/json"`.
79 |
80 | ## As a component
81 |
82 | The classic interface to React Async. Simply use `` directly in your JSX component tree, leveraging the render props pattern:
83 |
84 | ```jsx
85 | import Async from "react-async"
86 |
87 | // Your promiseFn receives all props from Async and an AbortController instance
88 | const loadPlayer = async ({ playerId }, { signal }) => {
89 | const res = await fetch(`/api/players/${playerId}`, { signal })
90 | if (!res.ok) throw new Error(res.statusText)
91 | return res.json()
92 | }
93 |
94 | const MyComponent = () => (
95 |
96 | {({ data, error, isPending }) => {
97 | if (isPending) return "Loading..."
98 | if (error) return `Something went wrong: ${error.message}`
99 | if (data)
100 | return (
101 |
102 | Player data:
103 |
{JSON.stringify(data, null, 2)}
104 |
105 | )
106 | return null
107 | }}
108 |
109 | )
110 | ```
111 |
112 | > Using [helper components](usage.md#with-helper-components) can greatly improve readability of your render functions by not having to write all those conditional returns.
113 |
114 | ## As a factory
115 |
116 | You can also create your own component instances, allowing you to preconfigure them with options such as default `onResolve` and `onReject` callbacks.
117 |
118 | ```jsx
119 | import { createInstance } from "react-async"
120 |
121 | const loadPlayer = async ({ playerId }, { signal }) => {
122 | const res = await fetch(`/api/players/${playerId}`, { signal })
123 | if (!res.ok) throw new Error(res.statusText)
124 | return res.json()
125 | }
126 |
127 | // createInstance takes a defaultOptions object and a displayName (both optional)
128 | const AsyncPlayer = createInstance({ promiseFn: loadPlayer }, "AsyncPlayer")
129 |
130 | const MyComponent = () => (
131 |
132 | {player => `Hello ${player.name}`}
133 |
134 | )
135 | ```
136 |
137 | ## With helper components
138 |
139 | Several [helper components](usage.md#helper-components) are available to improve legibility. They can be used with `useAsync` by passing in the state, or with `` by using Context. Each of these components simply enables or disables rendering of its children based on the current state.
140 |
141 | ```jsx
142 | import { useAsync, IfPending, IfFulfilled, IfRejected } from "react-async"
143 |
144 | const loadPlayer = async ({ playerId }, { signal }) => {
145 | // ...
146 | }
147 |
148 | const MyComponent = () => {
149 | const state = useAsync({ promiseFn: loadPlayer, playerId: 1 })
150 | return (
151 | <>
152 | Loading...
153 | {error => `Something went wrong: ${error.message}`}
154 |
155 | {data => (
156 |
157 | Player data:
158 |
{JSON.stringify(data, null, 2)}
159 |
160 | )}
161 |
162 | >
163 | )
164 | }
165 | ```
166 |
167 | ### As compounds to ``
168 |
169 | Each of the helper components are also available as static properties of ``. In this case you won't have to pass the state object, instead it will be automatically provided through Context.
170 |
171 | ```jsx
172 | import Async from "react-async"
173 |
174 | const loadPlayer = async ({ playerId }, { signal }) => {
175 | const res = await fetch(`/api/players/${playerId}`, { signal })
176 | if (!res.ok) throw new Error(res.statusText)
177 | return res.json()
178 | }
179 |
180 | const MyComponent = () => (
181 |
182 | Loading...
183 |
184 | {data => (
185 |
186 | Player data:
187 |
{JSON.stringify(data, null, 2)}
188 |
189 | )}
190 |
191 | {error => `Something went wrong: ${error.message}`}
192 |
193 | )
194 | ```
195 |
--------------------------------------------------------------------------------
/docs/guide/async-actions.md:
--------------------------------------------------------------------------------
1 | # Async actions
2 |
3 | Fetching data for display alone isn't sufficient for most applications. You'll often also want to submit data back to
4 | the server, or handle other types of asynchronous actions. To enable this, React Async has the concept of a
5 | [`deferFn`](../api/options.md#deferfn).
6 |
7 | Like `promiseFn`, a `deferFn` is a function that returns a Promise. The difference is that `deferFn` will not be
8 | automatically invoked by React Async when rendering the component. Instead it will have to be triggered by calling the
9 | [`run`](../api/state.md#run) function provided by React Async.
10 |
11 | ```jsx
12 | import React, { useState } from "react"
13 | import { useAsync } from "react-async"
14 |
15 | const subscribe = ([email], props, { signal }) =>
16 | fetch("/newsletter", { method: "POST", body: JSON.stringify({ email }), signal })
17 |
18 | const NewsletterForm = () => {
19 | const { isPending, error, run } = useAsync({ deferFn: subscribe })
20 | const [email, setEmail] = useState("")
21 |
22 | const handleSubmit = event => {
23 | event.preventDefault()
24 | run(email)
25 | }
26 |
27 | return (
28 |
35 | )
36 | }
37 | ```
38 |
39 | As you can see, the `deferFn` is invoked with 3 arguments: `args`, `props` and the AbortController. `args` is an array
40 | representing the arguments that were passed to `run`. In this case we passed the `email`, so we can extract that from
41 | the `args` array at the first index using [array destructuring] and pass it along to our `fetch` request.
42 |
43 | [array destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring
44 |
45 | ## Sending data with `useFetch`
46 |
47 | The above example can be simplified when we rely on [`useFetch`](../api/interfaces.md#usefetch-hook) instead of
48 | constructing the request manually.
49 |
50 | ```jsx
51 | import React, { useState } from "react"
52 | import { useFetch } from "react-async"
53 |
54 | const NewsletterForm = () => {
55 | const { isPending, error, run } = useFetch("/newsletter", { method: "POST" })
56 | const [email, setEmail] = useState("")
57 |
58 | const handleSubmit = event => {
59 | event.preventDefault()
60 | run({ body: JSON.stringify({ email }) })
61 | }
62 |
63 | return (
64 |
71 | )
72 | }
73 | ```
74 |
75 | The [`run`](../api/state.md#run) function for `useFetch` is a little special because it allows you to override the
76 | request's resource and other params. This way you can pass in the body, add dynamic headers or override the URL.
77 |
--------------------------------------------------------------------------------
/docs/guide/async-components.md:
--------------------------------------------------------------------------------
1 | # Async components
2 |
3 | The most common use case for React Async is data fetching. In single-page applications it's very common to dynamically
4 | load some data from a backend. React Async makes it incredibly easy to set this up, without having to worry about the
5 | details.
6 |
7 | The mental model of React Async is component-first. Rather than loading data high up in your application and passing it
8 | down to a component for display, you perform the data loading at the component level. Such a component is called an
9 | async component. An async component can render its state in a meaningful way like any other component, or be logic-only.
10 | In that case it doesn't render any UI but instead passes its state down to its children. Such separation of concerns is
11 | good practice.
12 |
13 | ## Creating an async component with `useFetch`
14 |
15 | The easiest way to create an async component for data fetching is through the
16 | [`useFetch` hook](../api/interfaces.md#usefetch-hook):
17 |
18 | ```jsx
19 | import React from "react"
20 | import { useFetch } from "react-async"
21 |
22 | const Person = ({ id }) => {
23 | const { data, error } = useFetch(`https://swapi.co/api/people/${id}/`, {
24 | headers: { accept: "application/json" },
25 | })
26 | if (error) return error.message
27 | if (data) return `Hi, my name is ${data.name}!`
28 | return null
29 | }
30 |
31 | const App = () => {
32 | return
33 | }
34 | ```
35 |
36 | ## More flexibility with `useAsync`
37 |
38 | For most data fetching needs, `useFetch` is sufficient. However, sometimes you may want to take full control, for
39 | example if you want to combine multiple requests. In this case you can use the
40 | [`useAsync` hook](../api/interfaces.md#useasync-hook).
41 |
42 | The core concept of `useAsync` (and React Async in general), is the [`promiseFn`](../api/options.md#promisefn): a
43 | function that returns a `Promise`. It's the fundamental concept for modelling asynchronous operations. It enables React
44 | Async to take control over scheduling, the Promise lifecycle and things like (re)starting an operation on user action or
45 | other changes. We've deliberately chosen the `Promise` as our primitive, because it's natively supported and has various
46 | utility methods like `Promise.all`. That's also why you'll find our terminology closely follows the Promise [states and
47 | fates].
48 |
49 | The above example, written with `useAsync`, would look like this:
50 |
51 | ```jsx
52 | import React from "react"
53 | import { useAsync } from "react-async"
54 |
55 | const fetchPerson = async ({ id }, { signal }) => {
56 | const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
57 | if (!response.ok) throw new Error(response.status)
58 | return response.json()
59 | }
60 |
61 | const Person = ({ id }) => {
62 | const { data, error } = useAsync({ promiseFn: fetchPerson, id })
63 | if (error) return error.message
64 | if (data) return `Hi, my name is ${data.name}!`
65 | return null
66 | }
67 |
68 | const App = () => {
69 | return
70 | }
71 | ```
72 |
73 | Notice the incoming parameters to `fetchPerson`. The `promiseFn` will be invoked with a `props` object and an
74 | `AbortController`. `props` are the options you passed to `useAsync`, which is why you can access the `id` property
75 | using [object destructuring]. The `AbortController` is created by React Async to enable [abortable fetch], so the
76 | underlying request will be aborted when the promise is cancelled (e.g. when a new one starts or we leave the page). We
77 | have to pass its `AbortSignal` down to `fetch` in order to wire this up.
78 |
79 | [states and fates]: https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
80 | [object destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
81 | [abortable fetch]: https://developers.google.com/web/updates/2017/09/abortable-fetch
82 |
--------------------------------------------------------------------------------
/docs/guide/optimistic-updates.md:
--------------------------------------------------------------------------------
1 | # Optimistic updates
2 |
3 | A powerful pattern to improve your app's perceived performance is optimistic updates. When building an async action, you
4 | might be able to predict the outcome of the operation. If so, you can implement optimistic updates by proactively
5 | setting the `data` to the predicted value, when starting the async action. Once the action completes, it will update
6 | `data` to the actual value, probably the same value as predicted.
7 |
8 | The following example uses both `promiseFn` and `deferFn` along with [`setData`](../api/state.md#setdata) to implement
9 | optimistic updates.
10 |
11 | ```jsx
12 | import Async from "react-async"
13 |
14 | const getAttendance = () => fetch("/attendance").then(() => true, () => false)
15 | const updateAttendance = ([attend]) =>
16 | fetch("/attendance", { method: attend ? "POST" : "DELETE" }).then(() => attend, () => !attend)
17 |
18 | const AttendanceToggle = () => (
19 |
20 | {({ isPending, data: isAttending, run, setData }) => (
21 | {
24 | setData(!isAttending)
25 | run(!isAttending)
26 | }}
27 | disabled={isPending}
28 | />
29 | )}
30 |
31 | )
32 | ```
33 |
34 | Here we have a switch to toggle attentance for an event. Clicking the toggle will most likely succeed, so we can predict
35 | the value it will have after completion (because we're just flipping a boolean).
36 |
37 | Notice that React Async accepts both a `promiseFn` and a `deferFn` at the same time. This allows you to combine data
38 | fetching with performing actions. A typical example of where this is useful is with forms, where you first want to
39 | populate the fields with current values from the database, and send the new values back when submitting the form. Do
40 | note that `promiseFn` and `deferFn` operate on the same `data`, so they should both resolve to a similar kind of value.
41 |
--------------------------------------------------------------------------------
/docs/guide/separating-view-logic.md:
--------------------------------------------------------------------------------
1 | # Separating view and logic
2 |
3 | It's generally good practice to separate view components from logic components. Async components should preferably be
4 | logic-only. That means they don't render anything by themselves. Instead you can use the [render props] pattern to pass
5 | down the async state:
6 |
7 | ```jsx
8 | import React from "react"
9 | import { useAsync } from "react-async"
10 |
11 | const fetchPerson = async ({ id }, { signal }) => {
12 | const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
13 | if (!response.ok) throw new Error(response.statusText)
14 | return response.json()
15 | }
16 |
17 | const Person = ({ id }) => {
18 | const state = useAsync({ promiseFn: fetchPerson, id })
19 | return children(state)
20 | }
21 |
22 | const App = () => {
23 | return (
24 |
25 | {({ isPending, data, error }) => {
26 | if (isPending) return "Loading..."
27 | if (error) return
28 | if (data) return
29 | return null
30 | }}
31 |
32 | )
33 | }
34 | ```
35 |
36 | > `ErrorMessage` and `Greeting` would be separate view components defined elsewhere.
37 |
38 | [render props]: https://reactjs.org/docs/render-props.html
39 |
40 | ## Cleaning up the JSX
41 |
42 | You'll notice the render props pattern is very powerful, but can also lead to code that's hard to read and understand.
43 | To make your JSX more declarative and less cluttered, you can use the [``](../api/interfaces.md#async-component)
44 | component and its [state helpers](../api/helpers.md). These take away the need for `if/else` statements and `return`
45 | keywords in your JSX.
46 |
47 | ```jsx
48 | import React from "react"
49 | import Async from "react-async"
50 |
51 | const fetchPerson = async ({ id }, { signal }) => {
52 | const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
53 | if (!response.ok) throw new Error(response.statusText)
54 | return response.json()
55 | }
56 |
57 | const App = () => {
58 | return (
59 |
60 | Loading...
61 | {error => }
62 | {data => }
63 |
64 | )
65 | }
66 | ```
67 |
68 | You should know that these helper components do not have to be direct children of the `` component. Because they
69 | are automatically wired up using [Context], they can be placed anywhere down the component tree, so long as they are
70 | descendants. You can also use helpers of the same type, multiple times.
71 |
72 | Stand-alone versions of `` and the like are also available. However, these must be wired up manually by
73 | passing the `state` prop and are therefore only really useful when combined with one of the async hooks.
74 |
75 | [context]: https://reactjs.org/docs/context.html
76 |
--------------------------------------------------------------------------------
/docs/guide/server-side-rendering.md:
--------------------------------------------------------------------------------
1 | # Server-side rendering
2 |
3 | There's a good chance you're using React with Server-side rendering (SSR), as many applications require this to be
4 | successful. If you happen to be using Next.js, it's really easy to integrate React Async. The crux is in setting a
5 | [`initialValue`](../api/options.md#initialvalue), which is fetched server-side for initial page loads and passed along
6 | through rehydration.
7 |
8 | ```jsx
9 | import fetch from "isomorphic-unfetch"
10 |
11 | const fetchPerson = async ({ id }) => {
12 | const response = await fetch(`https://swapi.co/api/people/${id}/`)
13 | if (!response.ok) throw new Error(response.status)
14 | return response.json()
15 | }
16 |
17 | const Person = ({ id, person }) => (
18 |
19 | Loading...
20 | {error => }
21 | {data => }
22 |
23 | )
24 |
25 | Person.getInitialProps = async ({ req }) => {
26 | const id = req.params.id
27 | const person = await fetchPerson({ id })
28 | return { id, person }
29 | }
30 | ```
31 |
32 | If React Async is provided an `initialValue`, it will not invoke the `promiseFn` on mount. Instead it will use the
33 | `initialValue` to immediately set `data` or `error`, and render accordingly.
34 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | React Async is a utility belt for declarative promise resolution and data fetching. It makes it easy to handle
4 | asynchronous UI states, without assumptions about the shape of your data or the type of request. React Async consists of
5 | a React component and several hooks. You can use it with `fetch`, Axios or other data fetching libraries, even GraphQL.
6 |
7 | ## Rationale
8 |
9 | React Async is different in that it tries to resolve data as close as possible to where it will be used, while using
10 | declarative syntax, using just JSX and native promises. This is in contrast to systems like Redux where you would
11 | configure any data fetching or updates on a higher (application global) level, using a special construct
12 | (actions/reducers).
13 |
14 | React Async works well even in larger applications with multiple or nested data dependencies. It encourages loading data
15 | on-demand and in parallel at component level instead of in bulk at the route/page level. It's entirely decoupled from
16 | your routes, so it works well in complex applications that have a dynamic routing model or don't use routes at all.
17 |
18 | React Async is promise-based, so you can resolve anything you want, not just `fetch` requests.
19 |
20 | ## Concurrent React and Suspense
21 |
22 | The React team is currently working on a large rewrite called [Concurrent React], previously known as "Async React".
23 | Part of this rewrite is Suspense, which is a generic way for components to suspend rendering while they load data from a
24 | cache. It can render a fallback UI while loading data, much like ``.
25 |
26 | React Async has no direct relation to Concurrent React. They are conceptually close, but not the same. React Async is
27 | meant to make dealing with asynchronous business logic easier. Concurrent React will make those features have less
28 | impact on performance and usability. When Suspense lands, React Async will make full use of Suspense features. In fact,
29 | you can already **start using React Async right now**, and in a later update, you'll **get Suspense features for free**.
30 | In fact, React Async already has experimental support for Suspense, by passing the `suspense` option.
31 |
32 | [concurrent react]: https://reactjs.org/docs/concurrent-mode-intro.html
33 |
--------------------------------------------------------------------------------
/examples/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "plugin:prettier/recommended",
4 | "plugin:promise/recommended",
5 | "plugin:react/recommended"
6 | ],
7 | "parser": "babel-eslint",
8 | "parserOptions": {
9 | "ecmaFeatures": {
10 | "jsx": true
11 | }
12 | },
13 | "plugins": ["jest", "promise", "react", "react-hooks"],
14 | "rules": {
15 | "react/prop-types": "off",
16 | "react-hooks/rules-of-hooks": "error"
17 | },
18 | "settings": {
19 | "react": {
20 | "version": "detect"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/basic-fetch/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/examples/basic-fetch/README.md:
--------------------------------------------------------------------------------
1 | # Basic fetch with React Async
2 |
3 | This demonstrates a very simple HTTP GET using `fetch`, wrapped with React Async.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/basic-fetch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basic-fetch-example",
3 | "version": "10.0.0",
4 | "private": true,
5 | "homepage": "https://react-async.async-library.now.sh/examples/basic-fetch",
6 | "scripts": {
7 | "bootstrap": "yarn install",
8 | "postinstall": "relative-deps",
9 | "prestart": "relative-deps",
10 | "prebuild": "relative-deps",
11 | "pretest": "relative-deps",
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test",
15 | "now-build": "SKIP_PREFLIGHT_CHECK=true react-scripts build"
16 | },
17 | "dependencies": {
18 | "react": "16.11.0",
19 | "react-async": "^10.0.0",
20 | "react-async-devtools": "^10.0.0",
21 | "react-dom": "16.11.0",
22 | "react-scripts": "3.4.1"
23 | },
24 | "devDependencies": {
25 | "relative-deps": "0.2.0"
26 | },
27 | "relativeDependencies": {
28 | "react-async": "../../packages/react-async/pkg",
29 | "react-async-devtools": "../../packages/react-async-devtools/pkg"
30 | },
31 | "eslintConfig": {
32 | "extends": "react-app"
33 | },
34 | "browserslist": [
35 | ">0.2%",
36 | "not dead",
37 | "not ie <= 11",
38 | "not op_mini all"
39 | ],
40 | "engines": {
41 | "node": ">=8"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/basic-fetch/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/async-library/react-async/b55964d266b66354be9d10ee9d64a41f8c10415e/examples/basic-fetch/public/favicon.ico
--------------------------------------------------------------------------------
/examples/basic-fetch/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/basic-fetch/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 20px;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
5 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | .user {
11 | display: inline-block;
12 | margin: 20px;
13 | text-align: center;
14 | }
15 |
16 | .avatar {
17 | background: #eee;
18 | border-radius: 64px;
19 | width: 128px;
20 | height: 128px;
21 | }
22 |
23 | .name {
24 | margin-top: 10px;
25 | }
26 |
27 | .placeholder {
28 | opacity: 0.5;
29 | }
30 |
--------------------------------------------------------------------------------
/examples/basic-fetch/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Async from "react-async"
3 | import DevTools from "react-async-devtools"
4 | import ReactDOM from "react-dom"
5 | import "./index.css"
6 |
7 | const loadUser = ({ userId }) =>
8 | fetch(`https://reqres.in/api/users/${userId}`)
9 | .then(res => (res.ok ? res : Promise.reject(res)))
10 | .then(res => res.json())
11 |
12 | const UserPlaceholder = () => (
13 |