Skip to content

Commit ade0557

Browse files
authored
feat: add useDefault hook
2 parents 6ed4a30 + 85c84d9 commit ade0557

File tree

6 files changed

+101
-0
lines changed

6 files changed

+101
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
- [**State**](./docs/State.md)
120120
- [`createMemo`](./docs/createMemo.md) — factory of memoized hooks.
121121
- [`createReducer`](./docs/createReducer.md) — factory of reducer hooks with custom middleware.
122+
- [`useDefault`](./docs/useDefault.md) — returns the default value when state is `null` or `undefined`.
122123
- [`useGetSet`](./docs/useGetSet.md) — returns state getter `get()` instead of raw state.
123124
- [`useGetSetState`](./docs/useGetSetState.md) — as if [`useGetSet`](./docs/useGetSet.md) and [`useSetState`](./docs/useSetState.md) had a baby.
124125
- [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props.

docs/useDefault.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# `useDefault`
2+
3+
React state hook that returns the default value when state is null or undefined.
4+
5+
## Usage
6+
7+
```jsx
8+
import {useDefault} from 'react-use';
9+
10+
const Demo = () => {
11+
const initialUser = { name: 'Marshall' }
12+
const defaultUser = { name: 'Mathers' }
13+
const [user, setUser] = useDefault(defaultUser, initialUser);
14+
15+
return (
16+
<div>
17+
<div>User: {user.name}</div>
18+
<input onChange={e => setUser({ name: e.target.value })} />
19+
<button onClick={() => setUser(null)}>set to null</button>
20+
</div>
21+
);
22+
};
23+
```

src/__stories__/useDefault.story.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { storiesOf } from '@storybook/react';
2+
import * as React from 'react';
3+
import { useDefault } from '..';
4+
import ShowDocs from './util/ShowDocs';
5+
6+
const Demo = () => {
7+
const initialUser = { name: 'Marshall' };
8+
const defaultUser = { name: 'Mathers' };
9+
const [user, setUser] = useDefault(defaultUser, initialUser);
10+
11+
return (
12+
<div>
13+
<div>User: {user.name}</div>
14+
<input onChange={e => setUser({ name: e.target.value })} />
15+
<button onClick={() => setUser(null)}>set to null</button>
16+
</div>
17+
);
18+
};
19+
20+
storiesOf('State|useDefault', module)
21+
.add('Docs', () => <ShowDocs md={require('../../docs/useDefault.md')} />)
22+
.add('Demo', () => <Demo />);

src/__tests__/useDefault.test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { act, cleanup, renderHook } from 'react-hooks-testing-library';
2+
import useDefault from '../useDefault';
3+
4+
afterEach(cleanup);
5+
6+
describe('useDefault', () => {
7+
test('should be defined', () => {
8+
expect(useDefault).toBeDefined();
9+
});
10+
11+
const hook = renderHook(() => useDefault({ name: 'Marshall' }, { name: '' }));
12+
13+
test('should return initial state on initial render', () => {
14+
expect(hook.result.current[0].name).toBe('');
15+
});
16+
17+
test('should update state with correct value', () => {
18+
hook.rerender();
19+
act(() => {
20+
hook.result.current[1]({ name: 'Mathers' });
21+
});
22+
23+
expect(hook.result.current[0].name).toBe('Mathers');
24+
});
25+
26+
test('should return the default value when updated state is nil', () => {
27+
hook.rerender();
28+
act(() => {
29+
hook.result.current[1](null);
30+
});
31+
32+
expect(hook.result.current[0].name).toBe('Marshall');
33+
34+
act(() => {
35+
hook.result.current[1](undefined);
36+
});
37+
38+
expect(hook.result.current[0].name).toBe('Marshall');
39+
});
40+
});

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import useCounter from './useCounter';
1313
import useCss from './useCss';
1414
import useDebounce from './useDebounce';
1515
import useDeepCompareEffect from './useDeepCompareEffect';
16+
import useDefault from './useDefault';
1617
import useDrop from './useDrop';
1718
import useDropArea from './useDropArea';
1819
import useEffectOnce from './useEffectOnce';
@@ -94,6 +95,7 @@ export {
9495
useCss,
9596
useDebounce,
9697
useDeepCompareEffect,
98+
useDefault,
9799
useDrop,
98100
useDropArea,
99101
useEffectOnce,

src/useDefault.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useState } from 'react';
2+
3+
const useDefault = (defaultValue, initialValue): [any, (nextValue?: any) => void] => {
4+
const [value, setValue] = useState(initialValue);
5+
6+
if (value === undefined || value === null) {
7+
return [defaultValue, setValue];
8+
}
9+
10+
return [value, setValue];
11+
};
12+
13+
export default useDefault;

0 commit comments

Comments
 (0)