diff --git a/snippets/UseAsync.md b/snippets/UseAsync.md deleted file mode 100644 index 8c3ac1416..000000000 --- a/snippets/UseAsync.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: useAsync -tags: hooks,state,effect,intermediate ---- - -A hook that handles asynchronous calls. - -- Create a custom hook that takes a handler `function` and `options`. -- Use the `React.useState()` hook to initialize the `value`, `error` and `loading` state variables. -- Use the `React.useEffect()` hook to call `run()` method and update the state variables accordingly if `options.autoRun` set to true. -- Use the `run` function to manually trigger `handler` function. -- Return an object containting the `value`, `error` and `isLoading` state variables and `run` function. - -```jsx -const useAsync = (fn, options = {}) => { - const [value, setValue] = React.useState(null); - const [error, setError] = React.useState(null); - const [isLoading, setIsLoading] = React.useState(false); - - const autoRun = options.autoRun || false; - - const run = async (args = null) => { - try { - setIsLoading(true); - const value = await fn(args); - setIsLoading(false); - setError(null); - setValue(value); - } catch (error) { - setIsLoading(false); - setError(error); - setValue(null); - } - }; - - React.useEffect(() => { - if (autoRun) { - run(); - } - }, [autoRun]); - - return { - value, - error, - isLoading, - run, - }; -}; -``` - -```jsx -const App = () => { - const handleSubmit = args => { - // args { foo: bar } - const url = "https://jsonplaceholder.typicode.com/todos"; - return fetch(url).then(response => response.json()); - }; - - const submission = useAsync(handleSubmit, { autoRun: false }); - - return ( -
- -
{JSON.stringify(submission.value, null, 2)}
-
- ); -}; -``` diff --git a/snippets/useAsync.md b/snippets/useAsync.md new file mode 100644 index 000000000..9881094f3 --- /dev/null +++ b/snippets/useAsync.md @@ -0,0 +1,65 @@ +--- +title: useAsync +tags: hooks,state,reducer,advanced +--- + +A hook that handles asynchronous calls. + +- Create a custom hook that takes a handler function, `fn`. +- Define a reducer function and an initial state for the custom hook's state. +- Use the `React.useReducer()` hook to initialize the `state` variable and the `dispatch` function. +- Define a `run` function that will run the provided callback, `fn`, while using `dispatch` to update `state` as necessary. +- Return an object containting the the properties of `state` (`value`, `error` and `loading`) and the `run` function. + +```jsx +const useAsync = (fn) => { + const initialState = { loading: false, error: null, value: null }; + const stateReducer = (_, action) => { + switch (action.type) { + case 'start': + return { loading: true, error: null, value: null}; + case 'finish': + return { loading: false, error: null, value: action.value}; + case 'error': + return { loading: false, error: action.error, value: null}; + } + } + + const [state, dispatch] = React.useReducer(stateReducer, initialState); + + const run = async (args = null) => { + try { + dispatch({ type: 'start' }); + const value = await fn(args); + dispatch({ type: 'finish', value }); + } catch (error) { + dispatch({ type: 'error', error }); + } + }; + + return { ...state, run }; +}; +``` + +```jsx +const RandomImage = props => { + const imgFetch = useAsync(url => fetch(url).then(response => response.json())); + + return ( +
+ +
+ { imgFetch.loading &&
Loading...
} + { imgFetch.error &&
Error { imgFetch.error }
} + { imgFetch.value && avatar} +
+ ); +}; + +ReactDOM.render(, document.getElementById('root')); +```