Skip to content

Commit

Permalink
feat: add useDefault hook
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich authored Jul 26, 2019
2 parents 6ed4a30 + 85c84d9 commit ade0557
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
- [**State**](./docs/State.md)
- [`createMemo`](./docs/createMemo.md) — factory of memoized hooks.
- [`createReducer`](./docs/createReducer.md) — factory of reducer hooks with custom middleware.
- [`useDefault`](./docs/useDefault.md) — returns the default value when state is `null` or `undefined`.
- [`useGetSet`](./docs/useGetSet.md) — returns state getter `get()` instead of raw state.
- [`useGetSetState`](./docs/useGetSetState.md) — as if [`useGetSet`](./docs/useGetSet.md) and [`useSetState`](./docs/useSetState.md) had a baby.
- [`usePrevious`](./docs/usePrevious.md) — returns the previous state or props.
Expand Down
23 changes: 23 additions & 0 deletions docs/useDefault.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# `useDefault`

React state hook that returns the default value when state is null or undefined.

## Usage

```jsx
import {useDefault} from 'react-use';

const Demo = () => {
const initialUser = { name: 'Marshall' }
const defaultUser = { name: 'Mathers' }
const [user, setUser] = useDefault(defaultUser, initialUser);

return (
<div>
<div>User: {user.name}</div>
<input onChange={e => setUser({ name: e.target.value })} />
<button onClick={() => setUser(null)}>set to null</button>
</div>
);
};
```
22 changes: 22 additions & 0 deletions src/__stories__/useDefault.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { useDefault } from '..';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const initialUser = { name: 'Marshall' };
const defaultUser = { name: 'Mathers' };
const [user, setUser] = useDefault(defaultUser, initialUser);

return (
<div>
<div>User: {user.name}</div>
<input onChange={e => setUser({ name: e.target.value })} />
<button onClick={() => setUser(null)}>set to null</button>
</div>
);
};

storiesOf('State|useDefault', module)
.add('Docs', () => <ShowDocs md={require('../../docs/useDefault.md')} />)
.add('Demo', () => <Demo />);
40 changes: 40 additions & 0 deletions src/__tests__/useDefault.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { act, cleanup, renderHook } from 'react-hooks-testing-library';
import useDefault from '../useDefault';

afterEach(cleanup);

describe('useDefault', () => {
test('should be defined', () => {
expect(useDefault).toBeDefined();
});

const hook = renderHook(() => useDefault({ name: 'Marshall' }, { name: '' }));

test('should return initial state on initial render', () => {
expect(hook.result.current[0].name).toBe('');
});

test('should update state with correct value', () => {
hook.rerender();
act(() => {
hook.result.current[1]({ name: 'Mathers' });
});

expect(hook.result.current[0].name).toBe('Mathers');
});

test('should return the default value when updated state is nil', () => {
hook.rerender();
act(() => {
hook.result.current[1](null);
});

expect(hook.result.current[0].name).toBe('Marshall');

act(() => {
hook.result.current[1](undefined);
});

expect(hook.result.current[0].name).toBe('Marshall');
});
});
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import useCounter from './useCounter';
import useCss from './useCss';
import useDebounce from './useDebounce';
import useDeepCompareEffect from './useDeepCompareEffect';
import useDefault from './useDefault';
import useDrop from './useDrop';
import useDropArea from './useDropArea';
import useEffectOnce from './useEffectOnce';
Expand Down Expand Up @@ -94,6 +95,7 @@ export {
useCss,
useDebounce,
useDeepCompareEffect,
useDefault,
useDrop,
useDropArea,
useEffectOnce,
Expand Down
13 changes: 13 additions & 0 deletions src/useDefault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useState } from 'react';

const useDefault = (defaultValue, initialValue): [any, (nextValue?: any) => void] => {
const [value, setValue] = useState(initialValue);

if (value === undefined || value === null) {
return [defaultValue, setValue];
}

return [value, setValue];
};

export default useDefault;

0 comments on commit ade0557

Please sign in to comment.