HomeAbout Me

Make HTTP Requests

By Daniel Nguyen
Published in React JS
March 29, 2024
1 min read
Make HTTP Requests

Background

Our app wouldn’t be very interesting without the ability to request data from a backend for the user to view and interact with. The way to do this in the web is using HTTP with the window.fetch API. Here’s a quick simple example of that API in action:

window
.fetch('http://example.com/movies.json')
.then(response => {
return response.json()
})
.then(data => {
console.log(data)
})

All the HTTP methods are supported as well, for example, here’s how you would POST data:

window
.fetch('http://example.com/movies', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// if auth is required. Each API may be different, but
// the Authorization header with a token is common.
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data), // body data type must match "content-type" header
})
.then(response => {
return response.json()
})
.then(data => {
console.log(data)
})

If the request fails with an unsuccessful status code (>= 400), then the response object’s ok property will be false. It’s common to reject the promise in this case:

window.fetch(url).then(async response => {
const data = await response.json()
if (response.ok) {
return data
} else {
return Promise.reject(data)
}
})

It’s good practice to wrap window.fetch in your own function so you can set defaults (especially handy for authentication). Additionally, it’s common to have “clients” which build upon this wrapper for operations on different resources.

Integrating this kind of thing with React involves utilizing React’s useEffect hook for making the request and useState for managing the status of the request as well as the response data and error information.

You might consider making the network request in the event handler. In general I recommend to do all your side effects inside the useEffect. This is because in the event handler you don’t have any possibility to prevent race conditions, or to implement any cancellation mechanism.

📜 https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Full Example

  1. hook.js
import * as React from 'react'
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],
)
}
// Example usage:
// const {data, error, status, run} = useAsync()
// React.useEffect(() => {
// run(fetchPokemon(pokemonName))
// }, [pokemonName, run])
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 {
// using the same names that react-query uses for convenience
isIdle: status === 'idle',
isLoading: status === 'pending',
isError: status === 'rejected',
isSuccess: status === 'resolved',
setData,
setError,
error,
status,
data,
run,
reset,
}
}
export {useAsync}
/** @jsx jsx */
import {jsx} from '@emotion/core'
import * as React from 'react'
import Tooltip from '@reach/tooltip'
import {FaSearch} from 'react-icons/fa'
import {Input, BookListUL, Spinner} from './components/lib'
import {BookRow} from './components/book-row'
import {client} from './utils/api-client'
function DiscoverBooksScreen() {
const [status, setStatus] = React.useState('idle')
const [data, setData] = React.useState(null)
const [query, setQuery] = React.useState('')
const [queried, setQueried] = React.useState(false)
const isLoading = status === 'loading'
const isSuccess = status === 'success'
React.useEffect(() => {
if (!queried) {
return
}
setStatus('loading')
client(`books?query=${encodeURIComponent(query)}`).then(responseData => {
setData(responseData)
setStatus('success')
})
}, [query, queried])
function handleSearchSubmit(event) {
event.preventDefault()
setQueried(true)
setQuery(event.target.elements.search.value)
}
return (
<div
css={{maxWidth: 800, margin: 'auto', width: '90vw', padding: '40px 0'}}
>
<form onSubmit={handleSearchSubmit}>
<Input
placeholder="Search books..."
id="search"
css={{width: '100%'}}
/>
<Tooltip label="Search Books">
<label htmlFor="search">
<button
type="submit"
css={{
border: '0',
position: 'relative',
marginLeft: '-35px',
background: 'transparent',
}}
>
{isLoading ? <Spinner /> : <FaSearch aria-label="search" />}
</button>
</label>
</Tooltip>
</form>
{isSuccess ? (
data?.books?.length ? (
<BookListUL css={{marginTop: 20}}>
{data.books.map(book => (
<li key={book.id} aria-label={book.title}>
<BookRow key={book.id} book={book} />
</li>
))}
</BookListUL>
) : (
<p>No books found. Try another search.</p>
)
) : null}
</div>
)
}
export {DiscoverBooksScreen}

Tags

#BuildReactApp

Share

Previous Article
Performance

Table Of Contents

1
Background
2
Full Example

Related Posts

Cache Management
March 31, 2024
8 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media