Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow overriding the 'resource' argument of 'fetch' when invoking 'run'. #150

Merged
merged 1 commit into from
Sep 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Allow overriding the 'resource' argument of 'fetch' when invoking 'run'.
  • Loading branch information
ghengeveld committed Sep 28, 2019
commit 2b09d9cce201c21e638443f16f5da00d04de564b
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -695,13 +695,20 @@ throw an exception and crash if the promise rejects.

Runs the `deferFn`, passing any arguments provided as an array.

When used with `useFetch`, `run` has a different signature:
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`

This runs the `fetch` request using the provided `init`. If it's 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.
> `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.

#### `reload`

Expand Down
2 changes: 2 additions & 0 deletions packages/react-async/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ type AsyncInitialWithout<K extends keyof AsyncInitial<T>, T> =
| Omit<AsyncRejected<T>, K>

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(ignoredEvent: React.SyntheticEvent): void
Expand Down
26 changes: 15 additions & 11 deletions packages/react-async/src/useAsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,27 +173,31 @@ const parseResponse = (accept, json) => res => {
return accept === "application/json" ? res.json() : res
}

const useAsyncFetch = (input, init, { defer, json, ...options } = {}) => {
const method = input.method || (init && init.method)
const headers = input.headers || (init && init.headers) || {}
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) || {}
const accept = headers["Accept"] || headers["accept"] || (headers.get && headers.get("accept"))
const doFetch = (input, init) => globalScope.fetch(input, init).then(parseResponse(accept, json))
const doFetch = (resource, init) =>
globalScope.fetch(resource, init).then(parseResponse(accept, json))
const isDefer =
typeof defer === "boolean" ? defer : ["POST", "PUT", "PATCH", "DELETE"].indexOf(method) !== -1
const fn = isDefer ? "deferFn" : "promiseFn"
const identity = JSON.stringify({ input, init, isDefer })
const identity = JSON.stringify({ resource, init, isDefer })
const state = useAsync({
...options,
[fn]: useCallback(
(arg1, arg2, arg3) => {
const [override, signal] = arg3 ? [arg1[0], arg3.signal] : [undefined, arg2.signal]
if (typeof override === "object" && "preventDefault" in override) {
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(input, { signal, ...init })
return doFetch(runResource || resource, { signal, ...init })
}
return typeof override === "function"
? doFetch(input, { signal, ...override(init) })
: doFetch(input, { signal, ...init, ...override })
return typeof runInit === "function"
? doFetch(runResource || resource, { signal, ...runInit(init) })
: doFetch(runResource || resource, { signal, ...init, ...runInit })
},
[identity] // eslint-disable-line react-hooks/exhaustive-deps
),
Expand Down
48 changes: 41 additions & 7 deletions packages/react-async/src/useAsync.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ describe("useFetch", () => {
expect(json).toHaveBeenCalled()
})

test("calling `run` with a method argument allows to override `init` parameters", () => {
test("calling `run` with a callback as argument allows to override `init` parameters", () => {
const component = (
<Fetch input="/test" init={{ method: "POST" }}>
<Fetch input="/test" init={{ method: "POST", body: '{"name":"foo"}' }}>
{({ run }) => (
<button onClick={() => run(init => ({ ...init, body: '{"name":"test"}' }))}>run</button>
<button onClick={() => run(init => ({ ...init, body: '{"name":"bar"}' }))}>run</button>
)}
</Fetch>
)
Expand All @@ -216,22 +216,56 @@ describe("useFetch", () => {
fireEvent.click(getByText("run"))
expect(globalScope.fetch).toHaveBeenCalledWith(
"/test",
expect.objectContaining({ method: "POST", signal: abortCtrl.signal, body: '{"name":"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" }}>
{({ run }) => <button onClick={() => run({ body: '{"name":"test"}' })}>run</button>}
<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":"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>
)
const { getByText } = render(component)
expect(globalScope.fetch).not.toHaveBeenCalled()
fireEvent.click(getByText("run"))
expect(globalScope.fetch).toHaveBeenCalledWith(
"/bar",
expect.objectContaining({ signal: abortCtrl.signal })
)
})

test("overriding the `resource` can be combined with overriding `init`", () => {
const component = (
<Fetch input="/foo" init={{ method: "POST", body: '{"name":"foo"}' }}>
{({ run }) => (
<button onClick={() => run("/bar", 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(
"/bar",
expect.objectContaining({ method: "POST", signal: abortCtrl.signal, body: '{"name":"bar"}' })
)
})

Expand Down