diff --git a/README.md b/README.md index 3d3603c4..6138cc28 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ - [`useCookie`](./docs/useCookie.md) — provides way to read, update and delete a cookie. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usecookie--demo) - [`useCopyToClipboard`](./docs/useCopyToClipboard.md) — copies text to clipboard. - [`useDebounce`](./docs/useDebounce.md) — debounces a function. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-usedebounce--demo) + - [`useError`](./docs/useError.md) — error dispatcher. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/side-effects-useerror--demo) - [`useFavicon`](./docs/useFavicon.md) — sets favicon of the page. - [`useLocalStorage`](./docs/useLocalStorage.md) — manages a value in `localStorage`. - [`useLockBodyScroll`](./docs/useLockBodyScroll.md) — lock scrolling of the body element. diff --git a/docs/useError.md b/docs/useError.md new file mode 100644 index 00000000..5a506f6d --- /dev/null +++ b/docs/useError.md @@ -0,0 +1,34 @@ +# `useError` + +React side-effect hook that returns an error dispatcher. + +## Usage + +```jsx +import { useError } from 'react-use'; + +const Demo = () => { + const dispatchError = useError(); + + const clickHandler = () => { + dispatchError(new Error('Some error!')); + }; + + return ; +}; + +// In parent app +const App = () => ( + + + +); +``` + +## Reference + +```js +const dispatchError = useError(); +``` + +- `dispatchError` — Callback of type `(err: Error) => void` diff --git a/src/index.ts b/src/index.ts index dc6f65ff..d4730ca7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ export { default as useDropArea } from './useDropArea'; export { default as useEffectOnce } from './useEffectOnce'; export { default as useEnsuredForwardedRef, ensuredForwardRef } from './useEnsuredForwardedRef'; export { default as useEvent } from './useEvent'; +export { default as useError } from './useError'; export { default as useFavicon } from './useFavicon'; export { default as useFullscreen } from './useFullscreen'; export { default as useGeolocation } from './useGeolocation'; diff --git a/src/useError.ts b/src/useError.ts new file mode 100644 index 00000000..b437901f --- /dev/null +++ b/src/useError.ts @@ -0,0 +1,19 @@ +import { useState, useEffect, useCallback } from 'react'; + +const useError = (): ((err: Error) => void) => { + const [error, setError] = useState(null); + + useEffect(() => { + if (error) { + throw error; + } + }, [error]); + + const dispatchError = useCallback((err: Error) => { + setError(err); + }, []); + + return dispatchError; +}; + +export default useError; diff --git a/stories/useError.story.tsx b/stories/useError.story.tsx new file mode 100644 index 00000000..e42c8052 --- /dev/null +++ b/stories/useError.story.tsx @@ -0,0 +1,46 @@ +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { useError } from '../src'; +import ShowDocs from './util/ShowDocs'; + +class ErrorBoundary extends React.Component<{}, { hasError: boolean }> { + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error) { + return { hasError: true }; + } + + render() { + if (this.state.hasError) { + return ( +
+

Something went wrong.

+ +
+ ); + } + + return this.props.children; + } +} + +const Demo = () => { + const dispatchError = useError(); + + const clickHandler = () => { + dispatchError(new Error('Some error!')); + }; + + return ; +}; + +storiesOf('Side effects|useError', module) + .add('Docs', () => ) + .add('Demo', () => ( + + + + )); diff --git a/tests/useError.test.ts b/tests/useError.test.ts new file mode 100644 index 00000000..2e3af244 --- /dev/null +++ b/tests/useError.test.ts @@ -0,0 +1,26 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useError } from '../src'; + +const setup = () => renderHook(() => useError()); + +beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +it('should throw an error on error dispatch', () => { + const errorStr = 'some_error'; + + try { + const { result } = setup(); + + act(() => { + result.current(new Error(errorStr)); + }); + } catch (err) { + expect(err.message).toEqual(errorStr); + } +});