27
loading...
This website collects cookies to deliver better user experience
useAsync()
hook which I learned to build from Kent's Epic React Workshop looks like this:function useSafeDispatch(dispatch) {
const mounted = React.useRef(false)
React.useLayoutEffect(() => {
mounted.current = true
return () => (mounted.current = false)
}, [])
return React.useCallback(
(...args) => (mounted.current ? dispatch(...args) : void 0),
[dispatch],
)
}
const defaultInitialState = {status: 'idle', data: null, error: null}
function useAsync(initialState) {
const initialStateRef = React.useRef({
...defaultInitialState,
...initialState,
})
const [{status, data, error}, setState] = React.useReducer(
(s, a) => ({...s, ...a}),
initialStateRef.current,
)
const safeSetState = useSafeDispatch(setState)
const setData = React.useCallback(
data => safeSetState({data, status: 'resolved'}),
[safeSetState],
)
const setError = React.useCallback(
error => safeSetState({error, status: 'rejected'}),
[safeSetState],
)
const reset = React.useCallback(
() => safeSetState(initialStateRef.current),
[safeSetState],
)
const run = React.useCallback(
promise => {
if (!promise || !promise.then) {
throw new Error(
`The argument passed to useAsync().run must be a promise. Maybe a function that's passed isn't returning anything?`,
)
}
safeSetState({status: 'pending'})
return promise.then(
data => {
setData(data)
return data
},
error => {
setError(error)
return Promise.reject(error)
},
)
},
[safeSetState, setData, setError],
)
return {
isIdle: status === 'idle',
isLoading: status === 'pending',
isError: status === 'rejected',
isSuccess: status === 'resolved',
setData,
setError,
error,
status,
data,
run,
reset,
}
}
export {useAsync}
BookInfo
component below and make it more elegant and robust by blowing multiple lines of code.💣import * as React from 'react'
import {
fetchBook,
BookInfoFallback,
BookForm,
BookDataView,
ErrorFallback,
} from '../book'
function BookInfo({bookName}) {
const [status, setStatus] = React.useState('idle')
const [book, setBook] = React.useState(null)
const [error, setError] = React.useState(null)
React.useEffect(() => {
if (!bookName) {
return
}
setStatus('pending')
fetchBook(bookName).then(
book => {
setBook(book)
setStatus('resolved')
},
error => {
setError(error)
setStatus('rejected')
},
)
}, [bookName])
if (status === 'idle') {
return 'Submit a book'
} else if (status === 'pending') {
return <BookInfoFallback name={bookName} />
} else if (status === 'rejected') {
return <ErrorFallback error={error}/>
} else if (status === 'resolved') {
return <BookDataView book={book} />
}
throw new Error('This should be impossible')
}
function App() {
const [bookName, setBookName] = React.useState('')
function handleSubmit(newBookName) {
setBookName(newBookName)
}
return (
<div className="book-info-app">
<BookForm bookName={bookName} onSubmit={handleSubmit} />
<hr />
<div className="book-info">
<BookInfo bookName={bookName} />
</div>
</div>
)
}
export default App
fetchBook
fetches data from the API and results in Promise which returns book data on resolution and error on rejection.
BookInfoFallback
is your loader component that accepts bookName to display a nice loading effect.
BookForm
is a simple form component that takes data from users.
BookDataView
is a nice looking component that displays the Book data to the user.
ErrorFallback
to show nice looking UI with Error.
BookInfo
component which handles fetching of the bookData in the useEffect
hook which sets the state according to different conditions, it also handles the rendering of BookDataView
upon successful fetching, ErrorFallback
on failure, and BookInfoFallback
while loading."Talk is cheap, show me the code" moment.
import * as React from 'react'
import {
fetchBook,
BookInfoFallback,
BookForm,
BookDataView,
ErrorFallback,
} from '../book'
import useAsync from '../utils';
function BookInfo({bookName}) {
/////////////// Focus from here /////////////////
const {data: book, isIdle, isLoading, isError, error, run} = useAsync()
React.useEffect(() => {
if (!pokemonName) {
return
}
run(fetchPokemon(pokemonName))
}, [pokemonName, run])
if (isIdle) {
return 'Submit a book'
} else if (isLoading) {
return <BookInfoFallback name={bookName} />
} else if (isError) {
return <ErrorFallback error={error}/>
} else if (isSuccess) {
return <BookDataView book={book} />
}
//////////////// To here /////////////////
throw new Error('This should be impossible')
}
function App() {
const [bookName, setBookName] = React.useState('')
function handleSubmit(newBookName) {
setBookName(newBookName)
}
return (
<div className="book-info-app">
<BookForm bookName={bookName} onSubmit={handleSubmit} />
<hr />
<div className="book-info">
<BookInfo bookName={bookName} />
</div>
</div>
)
}
export default App
useAsync()
hook which demonstrates its use cases.