From 4e469c3ac649ce7213ccc5ec69584fb84dbe0059 Mon Sep 17 00:00:00 2001 From: leonardohabitzreuter Date: Mon, 22 Jul 2019 15:36:34 -0300 Subject: [PATCH 1/2] useDefault hook --- README.md | 1 + docs/useDefault.md | 23 ++++++++++++++++ src/__stories__/useDefault.story.tsx | 22 +++++++++++++++ src/__tests__/useDefault.test.tsx | 40 ++++++++++++++++++++++++++++ src/index.ts | 2 ++ src/useDefault.ts | 13 +++++++++ 6 files changed, 101 insertions(+) create mode 100644 docs/useDefault.md create mode 100644 src/__stories__/useDefault.story.tsx create mode 100644 src/__tests__/useDefault.test.tsx create mode 100644 src/useDefault.ts diff --git a/README.md b/README.md index 62a4f73c7a..758c4e2ffd 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/useDefault.md b/docs/useDefault.md new file mode 100644 index 0000000000..9ef0bc7de5 --- /dev/null +++ b/docs/useDefault.md @@ -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 ( +
+
User: {user.name}
+ setUser({ name: e.target.value })} /> + +
+ ); +}; +``` diff --git a/src/__stories__/useDefault.story.tsx b/src/__stories__/useDefault.story.tsx new file mode 100644 index 0000000000..3cf0bfef94 --- /dev/null +++ b/src/__stories__/useDefault.story.tsx @@ -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 ( +
+
User: {user.name}
+ setUser({ name: e.target.value })} /> + +
+ ); +}; + +storiesOf('State|useDefault', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/__tests__/useDefault.test.tsx b/src/__tests__/useDefault.test.tsx new file mode 100644 index 0000000000..97de7d4b5b --- /dev/null +++ b/src/__tests__/useDefault.test.tsx @@ -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'); + }); +}); diff --git a/src/index.ts b/src/index.ts index 07bee73325..6578d00d9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; @@ -94,6 +95,7 @@ export { useCss, useDebounce, useDeepCompareEffect, + useDefault, useDrop, useDropArea, useEffectOnce, diff --git a/src/useDefault.ts b/src/useDefault.ts new file mode 100644 index 0000000000..c552949b26 --- /dev/null +++ b/src/useDefault.ts @@ -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; From 85c84d9710c8f08a89c3003078a4116024a476e2 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 26 Jul 2019 22:09:30 +0200 Subject: [PATCH 2/2] docs: markup for code elements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 758c4e2ffd..73793bb9aa 100644 --- a/README.md +++ b/README.md @@ -119,7 +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. + - [`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.