Skip to content

Commit 9f39790

Browse files
authored
Add hook implementation and simplify API (#1)
- remove Lodash - Add useAutosave hook - Hit 100% testing coverage - docs
1 parent 38db11c commit 9f39790

File tree

10 files changed

+322
-215
lines changed

10 files changed

+322
-215
lines changed

README.md

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,66 @@
22

33
![Tests](https://github.com/jollyjerr/react-autosave/workflows/Tests/badge.svg)
44
[![codecov](https://codecov.io/gh/jollyjerr/react-autosave/branch/main/graph/badge.svg?token=K7C88VK5GE)](https://codecov.io/gh/jollyjerr/react-autosave)
5+
![npm](https://img.shields.io/npm/dm/react-autosave)
56

6-
> Auto save controlled form values as they are updated.
7-
8-
Credit where credit is due - this library was inspired from [this blog post](https://www.synthace.com/autosave-with-react-hooks/)
7+
> A super simple debouncing component/hook to auto save controlled form values as they are updated.
98
109
```jsx
1110
import React from "react";
1211
import axios from "axios";
1312

14-
import { Autosave } from "react-autosave";
13+
import { Autosave, useAutosave } from "react-autosave";
1514

16-
const updateBlog = (data) => axios.post("myapi/blog/123", {text: data});
15+
const updateBlog = (data) => axios.post("myapi/blog/123", { text: data });
1716

17+
// Via component
1818
const EditBlogForm = () => {
19-
const [blogText, setBlogText] = React.useState("hello world");
20-
return (
21-
<div>
22-
<input
23-
type="text"
24-
value={data}
25-
onChange={(e) => setBlogText(e.target.value)}
26-
/>
27-
<Autosave
28-
data={blogText}
29-
onSave={updateBlog}
30-
/>
31-
</div>
32-
);
19+
const [blogText, setBlogText] = React.useState("hello world");
20+
return (
21+
<div>
22+
<input
23+
type="text"
24+
value={blogText}
25+
onChange={(e) => setBlogText(e.target.value)}
26+
/>
27+
<Autosave data={blogText} onSave={updateBlog} />
28+
</div>
29+
);
30+
};
31+
32+
// Via hook
33+
const EditBlogFormWithHook = () => {
34+
const [blogText, setBlogText] = React.useState("hello world");
35+
useAutosave({ data: blogText, onSave: updateBlog });
36+
return (
37+
<div>
38+
<input
39+
type="text"
40+
value={blogText}
41+
onChange={(e) => setBlogText(e.target.value)}
42+
/>
43+
</div>
44+
);
3345
};
3446
```
3547

3648
react-autosave is an extremely lightweight component that periodically triggers an async callback function if, and only if, the value to update has changed.
3749

3850
## Features
3951

40-
1. Written in typescript. Generic Typescript support out of the box.
52+
1. Written in typescript.
4153

42-
2. Callback props for successful and failed api calls.
54+
2. Lightweight and simple.
4355

44-
3. Lightweight and simple.
56+
3. No external libraries.
4557

4658
## API
4759

48-
| Prop | Type | Description |
49-
|---------- |:-------------: |-------------:|
50-
| data | T | The controlled form value to be auto saved |
51-
| onSave | (data: T) => Promise | The callback function to save your data |
52-
| interval (optional) | number | The number of milliseconds between save attempts. Defaults to 2000 |
53-
| onError (optional) | Function | A callback function for if the save function errors |
54-
| onSuccess (optional) | Function | A callback function for if the save function resolves
60+
| Prop | Type | Description |
61+
| ------------------- | :------------------: | -----------------------------------------------------------------: |
62+
| data | T | The controlled form value to be auto saved |
63+
| onSave | (data: T) => Promise | The callback function to save your data |
64+
| interval (optional) | number | The number of milliseconds between save attempts. Defaults to 2000 |
5565

5666
### Contributing
5767

@@ -62,14 +72,3 @@ yarn
6272
```
6373

6474
The test suite can be run with `yarn test`
65-
66-
### Advanced usage
67-
68-
You can also type the internal mechanics with generics.
69-
70-
```tsx
71-
<Autosave<SomeCustomInterface,boolean>
72-
data={{} as SomeCustomInterface}
73-
onSave={(data: SomeCustomInterface) => Promise.resolve(false)}
74-
/>
75-
```

package.json

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,65 @@
11
{
2-
"name": "react-autosave",
3-
"version": "0.1.5",
4-
"description": "Autosave controlled form values with style",
5-
"repository": "https://github.com/jollyjerr/react-autosave",
6-
"author": "jollyjerr <[email protected]>",
7-
"license": "MIT",
8-
"main": "build/index.js",
9-
"module": "build/index.es.js",
10-
"files": [
11-
"Autosave.d.ts",
12-
"index.d.ts",
13-
"index.es.js",
14-
"index.js"
15-
],
16-
"types": "dist/index.d.ts",
17-
"private": false,
18-
"publishConfig": {
19-
"access": "public"
20-
},
21-
"keywords": [
22-
"react",
23-
"autosave",
24-
"form"
25-
],
26-
"scripts": {
27-
"build": "rollup -c",
28-
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
29-
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
30-
"test": "jest",
31-
"prepublishOnly": "yarn build && cp -r ./build/* . && rm -rf ./build",
32-
"postpublish": "git clean -fd"
33-
},
34-
"dependencies": {
35-
"lodash": "^4.17.20"
36-
},
37-
"devDependencies": {
38-
"@babel/core": "^7.12.3",
39-
"@babel/preset-env": "^7.12.1",
40-
"@babel/preset-react": "^7.12.5",
41-
"@rollup/plugin-commonjs": "^16.0.0",
42-
"@rollup/plugin-node-resolve": "^10.0.0",
43-
"@testing-library/dom": "^7.26.5",
44-
"@testing-library/react": "^11.0.0",
45-
"@testing-library/user-event": "^12.2.0",
46-
"@types/jest": "^26.0.15",
47-
"@types/lodash": "^4.14.164",
48-
"@types/react": "^16.9.49",
49-
"@types/react-dom": "^16.9.8",
50-
"babel-jest": "^26.6.3",
51-
"jest": "^26.6.3",
52-
"prettier": "^2.0.5",
53-
"react": "^17.0.1",
54-
"react-dom": "^17.0.1",
55-
"react-test-renderer": "^17.0.1",
56-
"rollup": "^2.33.1",
57-
"rollup-plugin-peer-deps-external": "^2.2.4",
58-
"rollup-plugin-typescript2": "^0.29.0",
59-
"ts-jest": "^26.4.3",
60-
"tslib": "^2.0.3",
61-
"typescript": "^4.0.0"
62-
},
63-
"peerDependencies": {
64-
"react": "^17.x.x || ^16.x.x",
65-
"react-dom": "^17.x.x || ^16.x.x"
66-
}
67-
}
2+
"name": "react-autosave",
3+
"version": "0.2.0",
4+
"description": "A super simple debouncing component/hook to auto save controlled form values as they are updated",
5+
"repository": "https://github.com/jollyjerr/react-autosave",
6+
"author": "jollyjerr <[email protected]>",
7+
"license": "MIT",
8+
"main": "build/index.js",
9+
"module": "build/index.es.js",
10+
"files": [
11+
"Autosave.d.ts",
12+
"index.d.ts",
13+
"index.es.js",
14+
"index.js"
15+
],
16+
"types": "dist/index.d.ts",
17+
"private": false,
18+
"publishConfig": {
19+
"access": "public"
20+
},
21+
"keywords": [
22+
"react",
23+
"autosave",
24+
"form"
25+
],
26+
"scripts": {
27+
"build": "rollup -c",
28+
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
29+
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
30+
"test": "jest",
31+
"prepublishOnly": "yarn build && cp -r ./build/* . && rm -rf ./build",
32+
"postpublish": "git clean -fd"
33+
},
34+
"dependencies": {},
35+
"devDependencies": {
36+
"@babel/core": "^7.12.3",
37+
"@babel/preset-env": "^7.12.1",
38+
"@babel/preset-react": "^7.12.5",
39+
"@rollup/plugin-commonjs": "^16.0.0",
40+
"@rollup/plugin-node-resolve": "^10.0.0",
41+
"@testing-library/dom": "^7.26.5",
42+
"@testing-library/react": "^11.0.0",
43+
"@testing-library/user-event": "^12.2.0",
44+
"@types/jest": "^26.0.15",
45+
"@types/lodash": "^4.14.164",
46+
"@types/react": "^16.9.49",
47+
"@types/react-dom": "^16.9.8",
48+
"babel-jest": "^26.6.3",
49+
"jest": "^26.6.3",
50+
"prettier": "^2.0.5",
51+
"react": "^17.0.1",
52+
"react-dom": "^17.0.1",
53+
"react-test-renderer": "^17.0.1",
54+
"rollup": "^2.33.1",
55+
"rollup-plugin-peer-deps-external": "^2.2.4",
56+
"rollup-plugin-typescript2": "^0.29.0",
57+
"ts-jest": "^26.4.3",
58+
"tslib": "^2.0.3",
59+
"typescript": "^4.0.0"
60+
},
61+
"peerDependencies": {
62+
"react": "^17.x.x || ^16.x.x",
63+
"react-dom": "^17.x.x || ^16.x.x"
64+
}
65+
}

src/Autosave.test.tsx

Lines changed: 0 additions & 71 deletions
This file was deleted.

src/Autosave.tsx

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,25 @@
11
import React from "react";
2-
import debounce from "lodash/debounce";
2+
import { AutosaveProps } from "./props";
3+
import useDebounce from "./useDebounce";
34

4-
type Props<T, R> = {
5-
/** The controlled form value to be auto saved */
6-
data: T;
7-
/** Callback function to save your data */
8-
onSave: (data: T) => Promise<R>;
9-
/** The number of milliseconds between save attempts. Defaults to 2000 */
10-
interval?: number;
11-
/** A callback function for if the save function errors */
12-
onError?: Function;
13-
/** A callback function for if the save function resolves */
14-
onSuccess?: Function;
15-
};
16-
17-
const Autosave = <T extends unknown, R extends unknown>({
5+
const Autosave = <TData extends unknown, TReturn extends unknown>({
186
data,
197
onSave,
208
interval = 2000,
21-
onError = console.error,
22-
onSuccess,
23-
}: Props<T, R>): null => {
24-
// eslint-disable-next-line react-hooks/exhaustive-deps
25-
const debouncedSave = React.useCallback(
26-
debounce(async (data: T) => {
27-
try {
28-
const resp = await onSave(data);
29-
if (onSuccess) {
30-
onSuccess(resp);
31-
}
32-
} catch (error) {
33-
onError(error);
34-
}
35-
}, interval),
36-
[],
37-
);
9+
element = <></>,
10+
}: AutosaveProps<TData, TReturn>) => {
11+
const initialRender = React.useRef(true);
12+
const debouncedValueToSave = useDebounce(data, interval);
3813
React.useEffect(() => {
39-
if (data) {
40-
debouncedSave(data);
14+
if (initialRender.current) {
15+
initialRender.current = false;
16+
} else {
17+
if (debouncedValueToSave) {
18+
onSave(debouncedValueToSave);
19+
}
4120
}
42-
}, [data, debouncedSave]);
43-
return null;
21+
}, [debouncedValueToSave, interval, onSave]);
22+
return element;
4423
};
4524

4625
export default Autosave;

0 commit comments

Comments
 (0)