-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
6,724 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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,2 @@ | ||
patreon: sergiodxa | ||
custom: https://paypal.me/sergiodxa |
This file contains 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,21 @@ | ||
name: CI | ||
|
||
on: [push] | ||
|
||
jobs: | ||
test: | ||
name: Test | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v1 | ||
- name: Use Node.js | ||
uses: actions/setup-node@v1 | ||
with: | ||
node-version: 12 | ||
- name: Install | ||
run: yarn install | ||
- name: Test | ||
run: yarn test | ||
env: | ||
CI: true |
This file contains 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,19 @@ | ||
name: Publish | ||
|
||
on: | ||
release: | ||
types: [published] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v1 | ||
- uses: actions/setup-node@v1 | ||
with: | ||
node-version: 12 | ||
registry-url: https://registry.npmjs.org/ | ||
- run: yarn install | ||
- run: npm publish --access public | ||
env: | ||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} |
This file contains 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 |
---|---|---|
@@ -1,2 +1,194 @@ | ||
# use-mutation | ||
Run async mutations safely in React | ||
# useMutation ![CI](https://github.com/sergiodxa/use-mutation/workflows/CI/badge.svg) ![Publish](https://github.com/sergiodxa/use-mutation/workflows/Publish/badge.svg) | ||
|
||
Run function which cause a side-effect, specially useful to run requests against an API, inside a React component. | ||
|
||
## Usage | ||
|
||
Install it: | ||
|
||
```sh | ||
$ yarn add use-mutation | ||
``` | ||
|
||
Import it: | ||
|
||
```ts | ||
import useMutation from 'use-mutation'; | ||
``` | ||
|
||
Create a function which runs a mutation | ||
|
||
```tsx | ||
async function createComment({ | ||
authorId, | ||
comment, | ||
}: { | ||
authorId: number; | ||
comment: string; | ||
}) { | ||
const res = await fetch('/api/comments', { | ||
method: 'POST', | ||
body: JSON.stringify({ authorId, comment }), | ||
}); | ||
if (!res.ok) throw new Error(res.statusText); | ||
return await res.json(); | ||
} | ||
``` | ||
|
||
Use your function with `useMutation` | ||
|
||
```tsx | ||
function CommentForm({ authorId }) { | ||
const [comment, setComment] = React.useState(''); | ||
const [mutate, { status }] = useMutation(createComment, { | ||
onMutate(input) { | ||
// do something before the mutation run | ||
return () => { | ||
// rollback changes if the mutation failed | ||
}; | ||
}, | ||
onSuccess(result) { | ||
// do something once the mutation succeeded | ||
}, | ||
onFailure(reason, rollback) { | ||
// do something once the mutation failed | ||
}, | ||
onSettled(reason, result, rollback) { | ||
if (reason) { | ||
// do something if the mutation failed | ||
} else { | ||
// do something if the mutation succeeded | ||
} | ||
}, | ||
}); | ||
|
||
const handleSubmit = React.useCallback( | ||
function handleSubmit(event) { | ||
mutate({ authorId, comment }); | ||
}, | ||
[mutate, comment] | ||
); | ||
|
||
// render your UI | ||
} | ||
``` | ||
|
||
### Usage with SWR | ||
|
||
If you are using SWR, you can use `useMutation` to run your mutations to perform Optimistic UI changes. | ||
|
||
```tsx | ||
import { cache, mutate } from 'swr'; | ||
|
||
function createComment(input) { | ||
// ... | ||
} | ||
|
||
function useCreateComment() { | ||
return useMutation(createComment, { | ||
onMutate(input) { | ||
const oldData = cache.get('comment-list'); | ||
// optimistically update the data before your mutation is run | ||
mutate('comment-list', current => current.concat(input), false); | ||
return () => mutate('comment-list', oldData, false); // revalidate if it failed | ||
}, | ||
}); | ||
} | ||
``` | ||
|
||
This way when you run `mutate`, it will first optimistically update your SWR cache and if it fails it will rollback to the old data. | ||
|
||
## API Reference | ||
|
||
```tsx | ||
const [mutate, { status, data, error, reset }] = useMutation< | ||
Input, | ||
Data, | ||
Error | ||
>(mutationFn, { | ||
onMutate, | ||
onSuccess, | ||
onFailure, | ||
onSettled, | ||
throwOnFailure, | ||
useErrorBoundary, | ||
}); | ||
|
||
const promise = mutate(input, { | ||
onSuccess, | ||
onSettled, | ||
onError, | ||
throwOnFailure, | ||
}); | ||
``` | ||
|
||
### Hook Generic | ||
|
||
> Only if you are using TypeScript | ||
- `Input = any` | ||
- The data your mutation function needs to run | ||
- `Data = any` | ||
- The data the hook will return as result of your mutation | ||
- `Error = any` | ||
- The error the hook will return as a failure in your mutation | ||
|
||
### Hook Options | ||
|
||
- `mutationFn(input: Input): Promise<Data>` | ||
- **Required** | ||
- A function to be executed before the mutation runs. | ||
- It receives the same input as the mutate function. | ||
- It can be an async or sync function, in both cases if it returns a function it will keep it as a way to rollback the changed applied inside onMutate. | ||
- `onMutate?(input: Input): Promise<rollbackFn | undefined> | rollbackFn | undefined` | ||
- Optional | ||
- A function to be executed before the mutation runs. | ||
- It receives the same input as the mutate function. | ||
- It can be an async or sync function, in both cases if it returns a function. | ||
- it will keep it as a way to rollback the changed applied inside `onMutate` | ||
- `onSuccess?(result: Data): Promise<void> | void` | ||
- Optional | ||
- A function to be executed after the mutation resolves successfully. | ||
- It receives the result of the mutation. | ||
- If a Promise is returned, it will be awaited before proceeding. | ||
- `onFailure?(reason: Error, rollback: rollbackFn): Promise<void> | void` | ||
- Optional | ||
- A function to be executed after the mutation failed to execute. | ||
- If a Promise is returned, it will be awaited before proceeding. | ||
- `onSettled?(reason?: Error, result?: Data, rollback?: rollbackFn): Promise<void> | void` | ||
- Optional | ||
- A function to be executed after the mutation has resolves, either successfully or as failure. | ||
- This function receives the reason of the error or the result of the mutation. It follow the normal Node.js callback style. | ||
- If a Promise is returned, it will be awaited before proceeding. | ||
- `throwOnFailure?: boolean` | ||
- Optional | ||
- If defined as `true`, a failure in the mutation will cause the `mutate` function to throw. Disabled by default. | ||
- `useErrorBoundary?: boolean` (default false) | ||
- Optional | ||
- If defined as `true`, a failure in the mutation will cause the Hook to throw in render time, making error boundaries catch the error. | ||
|
||
### Hook Returned Value | ||
|
||
- `mutate(input: Input, config: Omit<Options<Input, Data, Error>, 'onMutate' | 'useErrorBoundary'> = {}): Promise<Data | undefined>` | ||
- The function you call to trigger your mutation, passing the input your mutation function needs. | ||
- All the lifecycle callback defined here will run _after_ the callback defined in the Hook. | ||
- `status: 'idle' | 'running' | 'success' | 'failure'` | ||
- The current status of the mutation, it will be: | ||
- `idle` initial status of the hook, and the status after a reset | ||
- `running` if the mutation is currently running | ||
- `success` if the mutation resolved successfully | ||
- `failure` if the mutation failed to resolve | ||
- `data: Data` | ||
- The data returned as the result of the mutation. | ||
- `error: Error` | ||
- The error returned as the result of the mutation. | ||
- `reset(): void | ||
- A function to reset the internal state of the Hook to the orignal idle and clear any data or error. | ||
|
||
## Author | ||
|
||
- [Sergio Xalambrí](https://sergiodxa.com) - [Able](https://able.co) | ||
|
||
## License | ||
|
||
The MIT License. |
This file contains 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,57 @@ | ||
{ | ||
"name": "use-mutation", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
"author": "Sergio Xalambrí", | ||
"main": "dist/index.js", | ||
"module": "dist/a.esm.js", | ||
"typings": "dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"src" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "sergiodxa/use-mutation" | ||
}, | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test": "tsdx test --passWithNoTests", | ||
"lint": "tsdx lint", | ||
"prepare": "tsdx build" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"prettier": { | ||
"printWidth": 80, | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"devDependencies": { | ||
"@testing-library/react": "^10.4.7", | ||
"@testing-library/user-event": "^12.0.12", | ||
"@types/react": "^16.9.43", | ||
"@types/react-dom": "^16.9.8", | ||
"husky": "^4.2.5", | ||
"mutation-observer": "^1.0.3", | ||
"react": "^16.13.1", | ||
"react-dom": "^16.13.1", | ||
"tsdx": "^0.13.2", | ||
"tslib": "^2.0.0", | ||
"typescript": "^3.9.7" | ||
}, | ||
"peerDependencies": { | ||
"react": ">=16" | ||
}, | ||
"dependencies": { | ||
"use-safe-callback": "^1.0.1" | ||
} | ||
} |
Oops, something went wrong.