Skip to content
Permalink
Browse files

Merge branch 'master' into next

  • Loading branch information...
ghengeveld committed Oct 7, 2019
2 parents 1223710 + 8aa1417 commit 3f51efd384c0c0b6b9b9a1fb70a88937c4f57323
@@ -88,11 +88,10 @@ Use it with `fetch`, Axios or other data fetching libraries, even GraphQL.

[abortable fetch]: https://developers.google.com/web/updates/2017/09/abortable-fetch

> ## Upgrading to v8
> ## Upgrading to v9
>
> Version 8 comes with breaking changes.
> Version 9 comes with a minor breaking change.
> See [Upgrading](https://docs.react-async.com/installation#upgrading) for details.
> A [codemod](https://github.com/async-library/react-async/tree/master/codemods) is available.
# Documentation

@@ -108,9 +108,12 @@ The number of times a promise was started.

> `Promise`
A reference to the internal wrapper promise created when starting a new promise \(either automatically or by invoking `run` / `reload`\). It fulfills or rejects along with the provided `promise` / `promiseFn` / `deferFn`. Useful as a chainable alternative to the `onResolve` / `onReject` callbacks.
A reference to the internal wrapper promise created when starting a new promise \(either automatically or by invoking
`run` / `reload`\). It fulfills or rejects along with the provided `promise` / `promiseFn` / `deferFn`. Useful as a
chainable alternative to the `onResolve` / `onReject` callbacks.

Warning! If you chain on `promise`, you MUST provide a rejection handler \(e.g. `.catch(...)`\). Otherwise React will throw an exception and crash if the promise rejects.
Warning! If you chain on `promise`, you MUST provide a rejection handler \(e.g. `.catch(...)`\). Otherwise React will
throw an exception and crash if the promise rejects.

## `run`

@@ -120,15 +123,18 @@ Runs the `deferFn`, passing any arguments provided as an array.

When used with `useFetch`, `run` has several overloaded signatures:

> `function(resource: String | Resource, init: Object | (init: Object) => Object): void`
>
> `function(init: Object | (init: Object) => Object): void`
> `function(override: OverrideParams | (params: OverrideParams) => OverrideParams): void`
>
> `function(event: SyntheticEvent | Event): void`
>
> `function(): void`
This way you can run the `fetch` request using the provided `resource` and `init`. `resource` can be omitted. If `init` is an object it will be spread over the default `init` \(`useFetch`'s 2nd argument\). If it's a function it will be invoked with the default `init` and should return a new `init` object. This way you can either extend or override the value of `init`, for example to set request headers.
Where `type OverrideParams = { resource?: RequestInfo } & Partial<RequestInit>`.

This way you can run the `fetch` request with custom `resource` and `init`. If `override` is an object it will be spread
over the default `resource` and `init` for `fetch`. If it's a function it will be invoked with the params defined with
`useFetch`, and should return an `override` object. This way you can either extend or override the value of `resource`
and `init`, for example to change the URL or set custom request headers.

## `reload`

@@ -1,5 +1,11 @@
# Upgrading

## Upgrade to v9

The rejection value for failed requests with `useFetch` was changed. Previously it was the Response object. Now it's an
Error object with `response` property. If you are using `useFetch` and are using the `error` value, expecting it to be
of type Response, you must now use `error.response` instead.

## Upgrade to v8

All standalone helper components were renamed to avoid import naming collision.
@@ -12,7 +18,8 @@ All standalone helper components were renamed to avoid import naming collision.

> A [codemod](https://github.com/async-library/react-async/tree/master/codemods) is available to automate the upgrade.
The return type for `run` was changed from `Promise` to `undefined`. You should now use the `promise` prop instead. This is a manual upgrade. See [`promise`](installation.md#promise-1) for details.
The return type for `run` was changed from `Promise` to `undefined`. You should now use the `promise` prop instead. This
is a manual upgrade. See [`promise`](state.md#promise) for details.

## Upgrade to v6

@@ -25,11 +32,8 @@ The return type for `run` was changed from `Promise` to `undefined`. You should
## Upgrade to v4

- `deferFn` now receives an `args` array as the first argument, instead of arguments to `run` being spread at the front

of the arguments list. This enables better interop with TypeScript. You can use destructuring to keep using your

existing variables.

- The shorthand version of `useAsync` now takes the `options` object as optional second argument. This used to be

`initialValue`, but was undocumented and inflexible.
@@ -227,11 +227,11 @@ type AsyncInitialWithout<K extends keyof AsyncInitial<T>, T> =
| Omit<AsyncFulfilled<T>, K>
| Omit<AsyncRejected<T>, K>

type OverrideParams = { resource?: RequestInfo } & Partial<RequestInit>

type FetchRun<T> = {
run(overrideResource: RequestInfo, overrideInit: (init: RequestInit) => RequestInit): void
run(overrideResource: RequestInfo, overrideInit: Partial<RequestInit>): void
run(overrideInit: (init: RequestInit) => RequestInit): void
run(overrideInit: Partial<RequestInit>): void
run(overrideParams: (params?: OverrideParams) => OverrideParams): void
run(overrideParams: OverrideParams): void
run(ignoredEvent: React.SyntheticEvent): void
run(ignoredEvent: Event): void
run(): void
@@ -1,6 +1,27 @@
import { getInitialStatus, getIdleStatus, getStatusProps, statusTypes } from "./status"

export const neverSettle = new Promise(() => {})
// This exists to make sure we don't hold any references to user-provided functions
class NeverSettle extends Promise {
constructor() {
super(() => {}, () => {})
/* istanbul ignore next */
if (Object.setPrototypeOf) {
// Not available in IE 10, but can be polyfilled
Object.setPrototypeOf(this, NeverSettle.prototype)
}
}
finally() {
return this
}
catch() {
return this
}
then() {
return this
}
}

export const neverSettle = new NeverSettle()

export const actionTypes = {
start: "start",
@@ -168,6 +168,11 @@ const useAsync = (arg1, arg2) => {
export class FetchError extends Error {
constructor(response) {
super(`${response.status} ${response.statusText}`)
/* istanbul ignore next */
if (Object.setPrototypeOf) {
// Not available in IE 10, but can be polyfilled
Object.setPrototypeOf(this, FetchError.prototype)
}
this.response = response
}
}
@@ -178,8 +183,6 @@ const parseResponse = (accept, json) => res => {
return accept === "application/json" ? res.json() : res
}

const isResource = value => typeof value === "string" || (typeof value === "object" && value.url)

const useAsyncFetch = (resource, init, { defer, json, ...options } = {}) => {
const method = resource.method || (init && init.method)
const headers = resource.headers || (init && init.headers) || {}
@@ -194,15 +197,17 @@ const useAsyncFetch = (resource, init, { defer, json, ...options } = {}) => {
...options,
[fn]: useCallback(
(arg1, arg2, arg3) => {
const [runArgs, signal] = isDefer ? [arg1, arg3.signal] : [[], arg2.signal]
const [runResource, runInit] = isResource(runArgs[0]) ? runArgs : [, runArgs[0]]
if (typeof runInit === "object" && "preventDefault" in runInit) {
// Don't spread Events or SyntheticEvents
return doFetch(runResource || resource, { signal, ...init })
const [override, signal] = isDefer ? [arg1[0], arg3.signal] : [undefined, arg2.signal]
const isEvent = typeof override === "object" && "preventDefault" in override
if (!override || isEvent) {
return doFetch(resource, { signal, ...init })
}
if (typeof override === "function") {
const { resource: runResource, ...runInit } = override({ resource, signal, ...init })
return doFetch(runResource || resource, { signal, ...runInit })
}
return typeof runInit === "function"
? doFetch(runResource || resource, { signal, ...runInit(init) })
: doFetch(runResource || resource, { signal, ...init, ...runInit })
const { resource: runResource, ...runInit } = override
return doFetch(runResource || resource, { signal, ...init, ...runInit })
},
[identity] // eslint-disable-line react-hooks/exhaustive-deps
),
@@ -211,6 +216,7 @@ const useAsyncFetch = (resource, init, { defer, json, ...options } = {}) => {
return state
}

/* istanbul ignore next */
const unsupported = () => {
throw new Error(
"useAsync requires React v16.8 or up. Upgrade your React version or use the <Async> component instead."
@@ -203,61 +203,27 @@ describe("useFetch", () => {
expect(json).toHaveBeenCalled()
})

test("calling `run` with a callback as argument allows to override `init` parameters", () => {
test("calling `run` with a callback as argument allows to override fetch parameters", () => {
const override = params => ({ ...params, resource: "/bar", body: '{"name":"bar"}' })
const component = (
<Fetch input="/test" init={{ method: "POST", body: '{"name":"foo"}' }}>
{({ run }) => (
<button onClick={() => run(init => ({ ...init, body: '{"name":"bar"}' }))}>run</button>
)}
</Fetch>
)
const { getByText } = render(component)
expect(globalScope.fetch).not.toHaveBeenCalled()
fireEvent.click(getByText("run"))
expect(globalScope.fetch).toHaveBeenCalledWith(
"/test",
expect.objectContaining({ method: "POST", signal: abortCtrl.signal, body: '{"name":"bar"}' })
)
})

test("calling `run` with an object as argument allows to override `init` parameters", () => {
const component = (
<Fetch input="/test" init={{ method: "POST", body: '{"name":"foo"}' }}>
{({ run }) => <button onClick={() => run({ body: '{"name":"bar"}' })}>run</button>}
</Fetch>
)
const { getByText } = render(component)
expect(globalScope.fetch).not.toHaveBeenCalled()
fireEvent.click(getByText("run"))
expect(globalScope.fetch).toHaveBeenCalledWith(
"/test",
expect.objectContaining({ method: "POST", signal: abortCtrl.signal, body: '{"name":"bar"}' })
)
})

test("calling `run` with a url allows to override fetch's `resource` parameter", () => {
const component = (
<Fetch input="/foo" options={{ defer: true }}>
{({ run }) => <button onClick={() => run("/bar")}>run</button>}
<Fetch input="/foo" init={{ method: "POST", body: '{"name":"foo"}' }}>
{({ run }) => <button onClick={() => run(override)}>run</button>}
</Fetch>
)
const { getByText } = render(component)
expect(globalScope.fetch).not.toHaveBeenCalled()
fireEvent.click(getByText("run"))
expect(globalScope.fetch).toHaveBeenCalledWith(
"/bar",
expect.objectContaining({ signal: abortCtrl.signal })
expect.objectContaining({ method: "POST", signal: abortCtrl.signal, body: '{"name":"bar"}' })
)
})

test("overriding the `resource` can be combined with overriding `init`", () => {
test("calling `run` with an object as argument allows to override fetch parameters", () => {
const override = { resource: "/bar", body: '{"name":"bar"}' }
const component = (
<Fetch input="/foo" init={{ method: "POST", body: '{"name":"foo"}' }}>
{({ run }) => (
<button onClick={() => run("/bar", init => ({ ...init, body: '{"name":"bar"}' }))}>
run
</button>
)}
{({ run }) => <button onClick={() => run(override)}>run</button>}
</Fetch>
)
const { getByText } = render(component)
@@ -1,3 +1,4 @@
/* eslint-disable react/prop-types */
import React, { Suspense } from "react"
import { storiesOf } from "@storybook/react"

0 comments on commit 3f51efd

Please sign in to comment.
You can’t perform that action at this time.