Skip to content

Commit 7ee9647

Browse files
committed
v2
- All value based hooks now are using arrays and tuples for better reference equality control - useMap hook added to manage Map in a nice and simple way - Migration guide from v1 to v2 added - Documentation updated - More tests covering optimizations etc.
1 parent f37b483 commit 7ee9647

File tree

3 files changed

+508
-113
lines changed

3 files changed

+508
-113
lines changed

README.md

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ const App = () => {
6262
);
6363
};
6464
```
65-
6665
### Example
6766

6867
[Open in CodeSandbox](https://codesandbox.io/s/44m70xm70)
@@ -72,6 +71,13 @@ const App = () => {
7271
Just an alternative syntax to `useState`, because it doesn't need array destructuring.
7372
It returns an object with `value` and a `setValue` method.
7473

74+
```typescript
75+
type UseStateful<T = any> = {
76+
value: T
77+
setValue: React.Dispatch<React.SetStateAction<T>>
78+
}
79+
```
80+
7581
```jsx
7682
const username = useStateful("test");
7783

@@ -113,10 +119,10 @@ const App = () => {
113119
### useBoolean
114120

115121
```jsx
116-
const showCounter = useBoolean(true);
122+
const [showCounter, actions] = useBoolean(true);
117123
```
118124

119-
Methods:
125+
Actions:
120126

121127
- `toggle`
122128
- `setTrue`
@@ -134,7 +140,7 @@ const rotatingNumber = useNumber(0, {
134140
});
135141
```
136142

137-
Methods:
143+
Actions:
138144

139145
Both `increase` and `decrease` take an optional `amount` argument which is 1 by default, and will override the `step` property if it's used in the options.
140146

@@ -150,37 +156,62 @@ Options:
150156

151157
### useInput
152158

153-
```jsx
154-
const newTodo = useInput("");
159+
This one is unique, since it returns tuple as a first element, where first element is `value` and second is `hasValue`
160+
Second element is `actions` as usual
161+
162+
```typescript
163+
type UseInputActions = {
164+
setValue: UseStateful<string>['setValue'] // Look above
165+
onChange: (e: React.SyntheticEvent) => void
166+
clear: () => void
167+
}
168+
type UseInput = [[string, boolean], UseInputActions]
155169
```
156170
157171
```jsx
158-
<input value={newTodo.value} onChange={newTodo.onChange} />
172+
const [[newTodo], actions] = useInput("");
159173
```
160174

161175
```jsx
162-
<input {...newTodo.bindToInput} />
163-
<Slider {...newTodo.bind} />
176+
<input value={newTodo} onChange={actions.onChange} />
164177
```
165-
166-
Methods:
178+
Actions:
167179

168180
- `clear`
169-
- `onChange`
170-
- `bindToInput` - binds the `value` and `onChange` props to an input that has `e.target.value`
171-
- `bind` - binds the `value` and `onChange` props to an input that's using only `e` in `onChange` (like most external components)
181+
- `onChange` - default native event.target.value handler
172182

173183
Properties:
174184

175-
- `hasValue`
185+
- `hasValue` -
186+
187+
### useBindToInput
188+
189+
Designed to be used in composition with `useInput`.
190+
First and second elements are the same as `useInput.
191+
Third are bindings to spread.
192+
193+
```jsx
194+
const [[newTodo], actions, { nativeBind, valueBind }] = useBindToInput(useInput(""));
195+
```
196+
197+
```jsx
198+
<input value={newTodo} onChange={actions.onChange} />
199+
<input {...nativeBind} />
200+
<Slider {...valueBind} />
201+
```
202+
203+
Actions:
204+
205+
- `nativeBind` - binds the `value` and `onChange` props to an input that has `e.target.value`
206+
- `valueBind` - binds the `value` and `onChange` props to an input that's using only `value` in `onChange` (like most external components)
176207

177208
### useArray
178209

179210
```jsx
180-
const todos = useArray([]);
211+
const [todos, actions] = useArray([]);
181212
```
182213

183-
Methods:
214+
Actions:
184215

185216
- `add`
186217
- `clear`
@@ -198,14 +229,30 @@ all of them will be removed
198229
-3 | -4 | [1, 3, 2, 4, 5]
199230
```
200231

232+
### useMap
233+
234+
```jsx
235+
const [someMap, someMapActions] = useMap([["key", "value"]]);
236+
const [anotherMap, anotherMapActions] = useMap(new Map([["key", "value"]]));
237+
```
238+
239+
Actions:
240+
241+
- `set`
242+
- `delete`
243+
- `clear`
244+
- `initialize` - applies tuples or map instances
245+
- `setValue`
246+
247+
201248
## useSetState
202249

203250
```jsx
204-
const { state, setState } = useSetState({ loading: false });
251+
const [state, setState] = useSetState({ loading: false });
205252
setState({ loading: true, data: [1, 2, 3] });
206253
```
207254

208-
Methods:
255+
Actions:
209256

210257
- `setState(value)` - will merge the `value` with the current `state` (like this.setState works in React)
211258

@@ -230,3 +277,51 @@ const Counter = () => {
230277
);
231278
};
232279
```
280+
281+
### Migration from v1 to v2
282+
283+
All value based hooks like `useBoolean`, `useNumber` etc. Are changed to
284+
be using arrays, since it's more safe for reference equality, and also
285+
makes it easier to use many `useSmth` without renaming `value` in destructuring.
286+
287+
So if you had
288+
```javascript
289+
const { value: showHeader, ...showHeaderActions } = useBoolean(true)
290+
const { value: showFooter, ...setShowFooterActions } = useBoolean(true)
291+
```
292+
It will become
293+
```javascript
294+
const [showHeader, showHeaderActions] = useBoolean(true)
295+
const [showFooter, showFooterActions] = useBoolean(true)
296+
```
297+
298+
Note that despite this code seems to be looking the same, it's not. Cause `showHeaderActions` in v1 will result
299+
in new object reference every rerender. While in v2 actions are memoized using `useMemo` and their reference will not
300+
change.
301+
It enables us passing `actions` down the props without useless re-renders, also it prevents `useEffects` and
302+
other hooks from re-run/new reference if autofix of ESLint rule `react-hooks/extraneous-deps` will add them as dependencies.
303+
304+
### useInput migration
305+
Also big change to the `useInput`
306+
If before you was not using `bind` and `bindToInput` from them, then using the same approach from above
307+
you will get what you want.
308+
But if you need bindings you need to compose `useInput` with `useBindToInput` like that:
309+
So if you had
310+
```jsx
311+
const { value, bindToInput, bind, onChange, hasValue } = useInput("")
312+
313+
<input value={value} onChange={onChange} />
314+
<input {...bindToInput} />
315+
{hasValue && <Slider {...bind} />}
316+
```
317+
It will become
318+
```jsx
319+
const [[value, hasValue], actions, { nativeBind, valueBind }] = useBindToInput(useInput(""))
320+
321+
<input value={value} onChange={actions.onChange} />
322+
<input {...nativeBind} />
323+
{hasValue && <Slider {...valueBind} />}
324+
```
325+
326+
Note that first element in destructured array has tuple of `[value, hasValue]` since it's for values
327+
and second argument is for `action` e.g. only for functions.

0 commit comments

Comments
 (0)