Skip to content

Commit

Permalink
Updating readme
Browse files Browse the repository at this point in the history
  • Loading branch information
dacre-denny committed Sep 26, 2019
1 parent 6c6840d commit ac27d69
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"indent": 0,
"react/jsx-key": 0,
"trailing-comma": 0,
"no-magic-numbers": ["error", { "ignore": [1, 2, 5, 10, 15] }],
"no-magic-numbers": ["error", { "ignore": [1, 2, 5, 7, 10, 12, 15, 19] }],
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/explicit-function-return-type": ["error"]
},
Expand Down
1 change: 0 additions & 1 deletion .nyc_output/4d19756e-1f9d-4042-acc0-05285e9d90e2.json

This file was deleted.

1 change: 1 addition & 0 deletions .nyc_output/7ec50c58-4e32-4191-8986-745600231a60.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"/home/dacre/mooce/github/use-setstate/src/index.ts":{"path":"/home/dacre/mooce/github/use-setstate/src/index.ts","statementMap":{"0":{"start":{"line":2,"column":0},"end":{"line":2,"column":62}},"1":{"start":{"line":3,"column":12},"end":{"line":3,"column":28}},"2":{"start":{"line":4,"column":16},"end":{"line":4,"column":36}},"3":{"start":{"line":5,"column":0},"end":{"line":34,"column":2}},"4":{"start":{"line":6,"column":13},"end":{"line":6,"column":36}},"5":{"start":{"line":6,"column":46},"end":{"line":6,"column":51}},"6":{"start":{"line":6,"column":64},"end":{"line":6,"column":69}},"7":{"start":{"line":7,"column":4},"end":{"line":23,"column":5}},"8":{"start":{"line":8,"column":23},"end":{"line":8,"column":42}},"9":{"start":{"line":9,"column":8},"end":{"line":22,"column":9}},"10":{"start":{"line":10,"column":12},"end":{"line":17,"column":24}},"11":{"start":{"line":11,"column":16},"end":{"line":16,"column":17}},"12":{"start":{"line":12,"column":20},"end":{"line":12,"column":36}},"13":{"start":{"line":15,"column":20},"end":{"line":15,"column":44}},"14":{"start":{"line":19,"column":13},"end":{"line":22,"column":9}},"15":{"start":{"line":20,"column":12},"end":{"line":20,"column":138}},"16":{"start":{"line":21,"column":12},"end":{"line":21,"column":36}},"17":{"start":{"line":24,"column":19},"end":{"line":32,"column":5}},"18":{"start":{"line":25,"column":8},"end":{"line":31,"column":9}},"19":{"start":{"line":26,"column":28},"end":{"line":26,"column":33}},"20":{"start":{"line":27,"column":12},"end":{"line":27,"column":39}},"21":{"start":{"line":30,"column":12},"end":{"line":30,"column":28}},"22":{"start":{"line":33,"column":4},"end":{"line":33,"column":29}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":22},"end":{"line":5,"column":23}},"loc":{"start":{"line":5,"column":51},"end":{"line":34,"column":1}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":28},"end":{"line":10,"column":29}},"loc":{"start":{"line":10,"column":40},"end":{"line":17,"column":13}},"line":10},"2":{"name":"(anonymous_2)","decl":{"start":{"line":24,"column":19},"end":{"line":24,"column":20}},"loc":{"start":{"line":24,"column":36},"end":{"line":32,"column":5}},"line":24}},"branchMap":{"0":{"loc":{"start":{"line":7,"column":4},"end":{"line":23,"column":5}},"type":"if","locations":[{"start":{"line":7,"column":4},"end":{"line":23,"column":5}},{"start":{"line":7,"column":4},"end":{"line":23,"column":5}}],"line":7},"1":{"loc":{"start":{"line":9,"column":8},"end":{"line":22,"column":9}},"type":"if","locations":[{"start":{"line":9,"column":8},"end":{"line":22,"column":9}},{"start":{"line":9,"column":8},"end":{"line":22,"column":9}}],"line":9},"2":{"loc":{"start":{"line":11,"column":16},"end":{"line":16,"column":17}},"type":"if","locations":[{"start":{"line":11,"column":16},"end":{"line":16,"column":17}},{"start":{"line":11,"column":16},"end":{"line":16,"column":17}}],"line":11},"3":{"loc":{"start":{"line":19,"column":13},"end":{"line":22,"column":9}},"type":"if","locations":[{"start":{"line":19,"column":13},"end":{"line":22,"column":9}},{"start":{"line":19,"column":13},"end":{"line":22,"column":9}}],"line":19},"4":{"loc":{"start":{"line":25,"column":8},"end":{"line":31,"column":9}},"type":"if","locations":[{"start":{"line":25,"column":8},"end":{"line":31,"column":9}},{"start":{"line":25,"column":8},"end":{"line":31,"column":9}}],"line":25}},"s":{"0":1,"1":1,"2":1,"3":1,"4":16,"5":16,"6":16,"7":16,"8":10,"9":10,"10":7,"11":7,"12":4,"13":3,"14":3,"15":1,"16":1,"17":16,"18":9,"19":2,"20":2,"21":7,"22":16},"f":{"0":16,"1":7,"2":9},"b":{"0":[10,6],"1":[7,3],"2":[4,3],"3":[1,2],"4":[2,7]},"_coverageSchema":"43e27e138ebf9cfc5966b082cf9a028302ed4184","hash":"dd73231e150c4d77c2043cb4d99c8733c3989e29","contentHash":"9f7045eaa06825adc060f9da73273da301c3337d413419c322f11f5259390414"},"/home/dacre/mooce/github/use-setstate/src/helpers.ts":{"path":"/home/dacre/mooce/github/use-setstate/src/helpers.ts","statementMap":{"0":{"start":{"line":2,"column":0},"end":{"line":2,"column":62}},"1":{"start":{"line":3,"column":0},"end":{"line":5,"column":2}},"2":{"start":{"line":4,"column":4},"end":{"line":4,"column":39}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":21},"end":{"line":3,"column":22}},"loc":{"start":{"line":3,"column":38},"end":{"line":5,"column":1}},"line":3}},"branchMap":{},"s":{"0":1,"1":1,"2":19},"f":{"0":19},"b":{},"_coverageSchema":"43e27e138ebf9cfc5966b082cf9a028302ed4184","hash":"e0893c423eb218bd693926393524e4bbda7d92a3","contentHash":"adde3b100aab0e6a8a58f7b61995e28e1edfd643a50457382d210b456ce7ac49"}}
1 change: 1 addition & 0 deletions .nyc_output/c1d53381-88ef-45ad-a268-fa8d7d71c372.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"/home/dacre/mooce/github/use-setstate/src/helpers.ts":{"path":"/home/dacre/mooce/github/use-setstate/src/helpers.ts","statementMap":{"0":{"start":{"line":14,"column":26},"end":{"line":16,"column":1}},"1":{"start":{"line":15,"column":2},"end":{"line":15,"column":37}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":14,"column":26},"end":{"line":14,"column":27}},"loc":{"start":{"line":14,"column":55},"end":{"line":16,"column":1}},"line":14}},"branchMap":{},"s":{"0":0,"1":0},"f":{"0":0},"b":{}}}
1 change: 0 additions & 1 deletion .nyc_output/fa4b9a45-1d35-468f-86b5-1a666930248f.json

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"uuid":"7ec50c58-4e32-4191-8986-745600231a60","parent":null,"pid":11582,"argv":["/home/dacre/.nvm/versions/node/v12.4.0/bin/node","/home/dacre/mooce/github/use-setstate/node_modules/.bin/mocha","-r","ts-node/register","-r","test/helpers","-b","test/index.spec.tsx"],"execArgv":[],"cwd":"/home/dacre/mooce/github/use-setstate","time":1569465627301,"ppid":11574,"root":"a8fcca7b-6f6e-47c6-8aa3-82e330559e46","coverageFilename":"/home/dacre/mooce/github/use-setstate/.nyc_output/7ec50c58-4e32-4191-8986-745600231a60.json","files":["/home/dacre/mooce/github/use-setstate/src/index.ts","/home/dacre/mooce/github/use-setstate/src/helpers.ts"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"uuid":"c1d53381-88ef-45ad-a268-fa8d7d71c372","parent":null,"pid":"11574","argv":["/home/dacre/.nvm/versions/node/v12.4.0/bin/node","/home/dacre/mooce/github/use-setstate/node_modules/.bin/nyc","mocha","-r","ts-node/register","-r","test/helpers","-b","test/index.spec.tsx"],"execArgv":[],"cwd":"/home/dacre/mooce/github/use-setstate","time":1569465626921,"ppid":null,"root":null,"coverageFilename":"/home/dacre/mooce/github/use-setstate/.nyc_output/c1d53381-88ef-45ad-a268-fa8d7d71c372.json","files":["/home/dacre/mooce/github/use-setstate/src/helpers.ts"]}

This file was deleted.

2 changes: 1 addition & 1 deletion .nyc_output/processinfo/index.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"processes":{"4d19756e-1f9d-4042-acc0-05285e9d90e2":{"parent":null,"children":[]},"fa4b9a45-1d35-468f-86b5-1a666930248f":{"parent":null,"children":[]}},"files":{"/home/dacre/mooce/github/react-setstate/src/index.ts":["4d19756e-1f9d-4042-acc0-05285e9d90e2"],"/home/dacre/mooce/github/react-setstate/src/helpers.ts":["4d19756e-1f9d-4042-acc0-05285e9d90e2","fa4b9a45-1d35-468f-86b5-1a666930248f"]},"externalIds":{}}
{"processes":{"7ec50c58-4e32-4191-8986-745600231a60":{"parent":null,"children":[]},"c1d53381-88ef-45ad-a268-fa8d7d71c372":{"parent":null,"children":[]}},"files":{"/home/dacre/mooce/github/use-setstate/src/index.ts":["7ec50c58-4e32-4191-8986-745600231a60"],"/home/dacre/mooce/github/use-setstate/src/helpers.ts":["7ec50c58-4e32-4191-8986-745600231a60","c1d53381-88ef-45ad-a268-fa8d7d71c372"]},"externalIds":{}}
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1 align="center">use-setstate</h1>
<p align="center">useState + setState = useSetState</p>
<p align="center">When hooks and setState collide</p>

<p align="center">
<a href="https://badge.fury.io/js/use-setstate"><img src="https://badge.fury.io/js/use-setstate.svg" alt="npm version" height="18"></a>
Expand All @@ -10,3 +10,87 @@
</p>

---

## Quick Start

```bash
npm install --save use-setstate
```

```jsx
import { useSetState } from "use-setstate";

/**
* Name input component with reset button. Dispatches an event of document when name
* value is updated.
*/
function NameField(props) {
// Declare state variable following useState convention
const [name, setName] = useSetState("", value => {
// State change callback is invoked when name state changes
document.dispatchEvent(new CustomEvent("user_updated_name", { details: { value } }));
});

return (
<>
<label>Name</label>
<input value={name} onChange={event => setName(event.target.value)} />
<button onClick={event => setName("")}>Reset</button>
</>
);
}
```

## Features

- Interface inspired by [useState](https://reactjs.org/docs/hooks-state.html)
- State change callback
- State setter can accept functions [like setState()](https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)
- Typescript support

## API

The API is similar to Reacts [`useState()`](https://reactjs.org/docs/hooks-state.html) hook. When `useSetState()` is called, an array is returned where the first item of the array is the current state value, and the second item of the array is a setter for that state:

```jsx
/*
let [value, setValue] = useState(initialValue?);
*/
let [value, setValue] = useSetState(initialValue?, callback?);
```
The main difference is the optional `callback` argument provided by `useSetState()`. This mimics the behavior of the Reacts optional [callback](https://reactjs.org/docs/react-component.html#setstate) for the `setState()` method:
```jsx
let [isOpen, setIsOpen] = useSetState(0, () => {
console.log("open state may have changed..");
});
```
If a state change callback is provided, `useSetState()` will pass the updated state as the first argumentinvoke the callback with the updated state as the first argument:
```jsx
let [, setMoney] = useSetState(0, money => {
if (money < 0) {
console.log("Uh oh..");
}
});
```
### State change callback
TODO
### Setter functions
TODO
## Run tests
```bash
npm run test
```
## License
Licensed under MIT
17 changes: 13 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,33 @@ import { isFunction, TypedFunction, FunctionOrValue, TypedValue, HookType } from
* @param callback
*/
export const useSetState = <T extends TypedValue>(initial?: FunctionOrValue<T>, callback?: TypedFunction<T, void>): HookType<T> => {
const [state, setState] = React.useState<T>(initial);
const [value, setValue] = React.useState<T>(initial);

if (callback !== undefined) {
const hasRun = React.useRef(false);

if (isFunction(callback)) {
React.useEffect((): void => {
if (hasRun.current) {
callback(state);
callback(value);
} else {
hasRun.current = true;
}
}, [state]);
}, [value]);
} else if (!hasRun.current) {
console.warn(`useSetState: function type for callback argument expected. Found callback of type "${typeof callback}"`);
hasRun.current = true;
}
}

return [state, setState];
const setState = (state: FunctionOrValue<T>): void => {
if (isFunction(state)) {
const transform = state as TypedFunction<T, T>;
setValue(transform(value));
} else {
setValue(state);
}
};

return [value, setState];
};
41 changes: 40 additions & 1 deletion test/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const validateHook = <T extends TypedValue>(hook: HookType<T>, initialValue: T):
expect(setValue).to.be.a("function");
};

describe("The setStateCallback hook", async (): Promise<void> => {
describe("The useSetState hook", async (): Promise<void> => {
afterEach(
async (): Promise<void> => {
sinon.restore();
Expand Down Expand Up @@ -159,4 +159,43 @@ describe("The setStateCallback hook", async (): Promise<void> => {
expect(callback.calledTwice).to.be.true;
expect(callback.calledWith(15)).to.be.true;
});

it("Should only invoke state change callback for state changes after state setter is called with callback", async (): Promise<void> => {
const callback = sinon.spy();
const transform = sinon.fake(n => n + 7);
const warn = sinon.stub(console, "warn");
const wrapper = mount(<HookWrapper hookProvider={(): HookType<number> => useSetState(5, callback)} />);

let { hook } = wrapper.find(HookDiv).props() as HookDivProps<number>;
let [, setValue] = hook;

validateHook(hook, 5);
expect(warn.called).to.be.false;

setValue(transform);
await tickUpdate(wrapper);

({ hook } = wrapper.find(HookDiv).props() as HookDivProps<number>);
[, setValue] = hook;

validateHook(hook, 12);
expect(warn.called).to.be.false;
expect(callback.calledOnce).to.be.true;
expect(callback.calledWith(12)).to.be.true;
expect(transform.calledOnce).to.be.true;
expect(transform.calledWith(5)).to.be.true;

setValue(transform);
await tickUpdate(wrapper);

({ hook } = wrapper.find(HookDiv).props() as HookDivProps<number>);
[, setValue] = hook;

validateHook(hook, 19);
expect(warn.called).to.be.false;
expect(callback.calledTwice).to.be.true;
expect(callback.calledWith(19)).to.be.true;
expect(transform.calledTwice).to.be.true;
expect(transform.calledWith(12)).to.be.true;
});
});

0 comments on commit ac27d69

Please sign in to comment.