Make Requests and handle Errors
Example usage:
function App() {
const [query, setQuery] = React.useState('')
function handleSubmit(query) {
setQuery(query)
}
return (
<div >
<Form query={query} onSubmit={handleSubmit} />
<Info query={query} />
</div>
)
}
For more advanced versions, see also the extra credit:
from: 6. useEffect: HTTP requests | React Hooks 🎣
See also:
function Info({query}) {
const [status, setStatus] = React.useState('idle')
const [data, setData] = React.useState(null)
const [error, setError] = React.useState(null)
React.useEffect(() => {
if (!query) {
return
}
setStatus('pending')
fetch('https://example.com/?q='+query)
.then(response => response.json())
.then(
data => {
setData(data)
setStatus('resolved')
},
error => {
setError(error)
setStatus('rejected')
},
)
}, [query])
if (status === 'idle') {
return 'Submit a something'
} else if (status === 'pending') {
return <InfoFallback query={query} />
} else if (status === 'rejected') {
return (
<div>
There was an error:{' '}
<pre style={{whiteSpace: 'normal'}}>{error.message}</pre>
</div>
)
} else if (status === 'resolved') {
return <DataView data={data} />
}
throw new Error('This should be impossible')
}
Alternative Make Request and handle Errors
This pattern does not only manage the state of the promise for fetching data, but also the fetching itself.
That makes it easier to use, but also less flexible.
Another difference is that this pattern only dispatches the most recent fetched data, while the other one dispatches all, e.g. an outdated fetch can overwrite a newer one.
A solution that fixes this in the other pattern with additional
upgrades (e.g. using useReducer,
useSafeDispatch, ...) can be found in the utililty:
useAync(initialState)
It uses useReducer similiar to the
exercise #2 of Advanced React Hooks
Example usage:
function App() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, doFetch] = useDataApi(
'https://hn.algolia.com/api/v1/search?query=redux',
{ hits: [] },
);
return (
<Fragment>
<form
onSubmit={event => {
doFetch(
`https://hn.algolia.com/api/v1/search?query=${query}`,
);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
from
How to fetch data with React Hooks? - RWieruch
with slide modifications:
My version replaced axios with native
fetch and also assumes that the return data is in json
format (response => response.json()).
const dataFetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false
};
case 'FETCH_SUCCESS':
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
case 'FETCH_FAILURE':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error();
}
};
const useDataApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const result = await fetch(url).then(response => response.json());
if (!didCancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: result });
}
} catch (error) {
if (!didCancel) {
dispatch({ type: 'FETCH_FAILURE' });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
return [state, setUrl];
};
Render as you fetch
A pattern to be used with
<React.Suspense/>
The
createResource
utility is intended to be used with this pattern.
Example usage:
function App() {
const [pokemonResource, setPokemonResource] = React.useState(null)
// ...
setPokemonResource(
createResource(fetch(pokemonName).then((response) => response.json()))
);
// ...
return (
<React.Suspense
fallback={<PokemonInfoFallback name={pokemonName} />}
>
<PokemonInfo pokemonResource={pokemonResource} />
</React.Suspense>
)
}
from: 2. Render as you fetch | React Suspense 🔀
See also:
import {createResource} from './utils'
function PokemonInfo({pokemonResource}) {
const pokemon = pokemonResource.read()
return (
<div>
<div className="pokemon-info__img-wrapper">
<img src={pokemon.image} alt={pokemon.name} />
</div>
<PokemonDataView pokemon={pokemon} />
</div>
)
}