-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: Hook for remote mutations #1450
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
Merged
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
1e2e10f
(wip) initial impl.
shuding 8ba262a
fix test
shuding 8383f03
(wip) fix deps
shuding bf9590f
merge master
shuding ac87cd0
initial implementation
shuding 4a8adbf
fix linter
shuding 4f9d67d
fix state reset
shuding 9c52ce6
Merge branch 'master' into mutation
shuding b57bf8a
avoid reset race condition
shuding 016cb47
fix race conditions
shuding 8d5c28f
code tweaks
shuding 3557505
code tweaks
shuding e485ae6
return bound mutate
shuding f047412
resolve conflicts
shuding d4a5ad3
apply review comments
shuding ad9212b
merge master
shuding 1f22be9
resolve conflicts
shuding c3b621f
fix tsconfig
shuding e481922
type fixes
shuding 67c9f36
fix lint errors
shuding c9fc40e
code tweaks
shuding dc79ba6
merge master
shuding 9f31280
resolve conflicts
shuding 4caa4b0
fix type error
shuding bedaddf
update types
shuding c7c1fc6
inline serialization result
shuding 31850e6
merge main
shuding e55a83d
Merge branch 'main' into mutation
shuding 2198dbe
merge main and update argument api
shuding cf0b795
add tests
shuding 11546fa
fix tests
shuding 94f3579
update typing
shuding be0d14a
update state api
shuding 76d7864
change error condition
shuding File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { useCallback, useRef } from 'react' | ||
import useSWR, { useSWRConfig, Middleware, Key } from 'swr' | ||
|
||
import { serialize } from '../src/utils/serialize' | ||
import { useStateWithDeps } from '../src/utils/state' | ||
import { withMiddleware } from '../src/utils/with-middleware' | ||
import { useIsomorphicLayoutEffect } from '../src/utils/env' | ||
import { UNDEFINED } from '../src/utils/helper' | ||
import { getTimestamp } from '../src/utils/timestamp' | ||
|
||
import { | ||
SWRMutationConfiguration, | ||
SWRMutationResponse, | ||
SWRMutationHook, | ||
MutationFetcher | ||
} from './types' | ||
|
||
const mutation = | ||
<Data, Error>() => | ||
( | ||
key: Key, | ||
fetcher: MutationFetcher<Data>, | ||
config: SWRMutationConfiguration<Data, Error> = {} | ||
) => { | ||
const { mutate } = useSWRConfig() | ||
|
||
const keyRef = useRef(key) | ||
// Ditch all mutation results that happened earlier than this timestamp. | ||
const ditchMutationsUntilRef = useRef(0) | ||
|
||
const [stateRef, stateDependencies, setState] = useStateWithDeps( | ||
{ | ||
data: UNDEFINED, | ||
error: UNDEFINED, | ||
isMutating: false | ||
}, | ||
true | ||
) | ||
const currentState = stateRef.current | ||
|
||
const trigger = useCallback( | ||
async (arg, opts?: SWRMutationConfiguration<Data, Error>) => { | ||
const [serializedKey, resolvedKey] = serialize(keyRef.current) | ||
|
||
if (!fetcher) { | ||
throw new Error('Can’t trigger the mutation: missing fetcher.') | ||
} | ||
if (!serializedKey) { | ||
throw new Error('Can’t trigger the mutation: key isn’t ready.') | ||
} | ||
|
||
// Disable cache population by default. | ||
const options = Object.assign({ populateCache: false }, config, opts) | ||
|
||
// Trigger a mutation, also track the timestamp. Any mutation that happened | ||
// earlier this timestamp should be ignored. | ||
const mutationStartedAt = getTimestamp() | ||
|
||
ditchMutationsUntilRef.current = mutationStartedAt | ||
|
||
setState({ isMutating: true }) | ||
|
||
try { | ||
const data = await mutate<Data>( | ||
serializedKey, | ||
(fetcher as any)(resolvedKey, { arg }), | ||
options | ||
) | ||
|
||
// If it's reset after the mutation, we don't broadcast any state change. | ||
if (ditchMutationsUntilRef.current <= mutationStartedAt) { | ||
setState({ data, isMutating: false }) | ||
options.onSuccess?.(data as Data, serializedKey, options) | ||
} | ||
return data | ||
} catch (error) { | ||
// If it's reset after the mutation, we don't broadcast any state change. | ||
if (ditchMutationsUntilRef.current <= mutationStartedAt) { | ||
setState({ error: error as Error, isMutating: false }) | ||
options.onError?.(error as Error, serializedKey, options) | ||
} | ||
throw error | ||
shuding marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[] | ||
) | ||
|
||
const reset = useCallback(() => { | ||
ditchMutationsUntilRef.current = getTimestamp() | ||
setState({ data: UNDEFINED, error: UNDEFINED, isMutating: false }) | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []) | ||
|
||
useIsomorphicLayoutEffect(() => { | ||
keyRef.current = key | ||
}) | ||
|
||
// We don't return `mutate` here as it can be pretty confusing (e.g. people | ||
// calling `mutate` but they actually mean `trigger`). | ||
// And also, `mutate` relies on the useSWR hook to exist too. | ||
return { | ||
trigger, | ||
reset, | ||
get data() { | ||
stateDependencies.data = true | ||
return currentState.data | ||
}, | ||
get error() { | ||
stateDependencies.error = true | ||
return currentState.error | ||
}, | ||
get isMutating() { | ||
stateDependencies.isMutating = true | ||
return currentState.isMutating | ||
} | ||
} | ||
} | ||
|
||
export default withMiddleware( | ||
useSWR, | ||
mutation as unknown as Middleware | ||
) as unknown as SWRMutationHook | ||
|
||
export { SWRMutationConfiguration, SWRMutationResponse } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "swr-mutation", | ||
"version": "0.0.1", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.esm.js", | ||
"types": "./dist/mutation", | ||
"peerDependencies": { | ||
"swr": "*", | ||
"react": "*" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"extends": "../tsconfig.json", | ||
"compilerOptions": { | ||
"rootDir": "..", | ||
"outDir": "./dist" | ||
}, | ||
"include": ["./*.ts"] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { SWRResponse, Key, MutatorOptions } from 'swr' | ||
|
||
type FetcherResponse<Data> = Data | Promise<Data> | ||
|
||
type FetcherOptions<ExtraArg = unknown> = Readonly<{ | ||
arg: ExtraArg | ||
}> | ||
|
||
export type MutationFetcher< | ||
Data = unknown, | ||
ExtraArg = unknown, | ||
SWRKey extends Key = Key | ||
> = SWRKey extends () => infer Arg | null | undefined | false | ||
? (key: Arg, options: FetcherOptions<ExtraArg>) => FetcherResponse<Data> | ||
: SWRKey extends null | undefined | false | ||
? never | ||
: SWRKey extends infer Arg | ||
? (key: Arg, options: FetcherOptions<ExtraArg>) => FetcherResponse<Data> | ||
: never | ||
|
||
export type SWRMutationConfiguration< | ||
Data, | ||
Error, | ||
ExtraArg = any, | ||
SWRMutationKey extends Key = Key | ||
> = MutatorOptions<Data> & { | ||
fetcher?: MutationFetcher<Data, ExtraArg, SWRMutationKey> | ||
onSuccess?: ( | ||
data: Data, | ||
key: string, | ||
config: Readonly< | ||
SWRMutationConfiguration<Data, Error, SWRMutationKey, ExtraArg> | ||
> | ||
) => void | ||
onError?: ( | ||
err: Error, | ||
key: string, | ||
config: Readonly< | ||
SWRMutationConfiguration<Data, Error, SWRMutationKey, ExtraArg> | ||
> | ||
) => void | ||
} | ||
|
||
export interface SWRMutationResponse< | ||
Data = any, | ||
Error = any, | ||
ExtraArg = any, | ||
SWRMutationKey extends Key = Key | ||
> extends Pick<SWRResponse<Data, Error>, 'data' | 'error'> { | ||
isMutating: boolean | ||
trigger: ( | ||
extraArgument?: ExtraArg, | ||
options?: SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey> | ||
) => Promise<Data | undefined> | ||
reset: () => void | ||
} | ||
|
||
export type SWRMutationHook = < | ||
Data = any, | ||
Error = any, | ||
SWRMutationKey extends Key = Key, | ||
ExtraArg = any | ||
>( | ||
...args: | ||
| readonly [SWRMutationKey, MutationFetcher<Data, ExtraArg, SWRMutationKey>] | ||
| readonly [ | ||
SWRMutationKey, | ||
MutationFetcher<Data, ExtraArg, SWRMutationKey>, | ||
SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey> | ||
] | ||
) => SWRMutationResponse<Data, Error, ExtraArg, SWRMutationKey> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.