Hey, React community, in this article, you will find how to create a simple-to-use HTTP hook for making HTTP calls on your React or React Native app.
First, let's create a simple hook
export function useHttp<T>() {}
- T is the type of data our hook and the HTTP response will return
Let's add some parameters
export function useHttp<T>(
request: HttpRequest<T>,
cb?: HttpCallback<T>,
config?: HttpConfig<T>,
): HttpHook<T> {}
- Request:
request: HttpRequest<T>
type HttpRequest<T> = (body?: any) => Promise<T>
The HttpRequest should be the actual API call our hook will execute. The body object is the data you will pass every time you make a request.
This could be simplified by passing an URL instead of a promise, but what if you want to call multiple APIs at the same time?
- Callback:
cb?: HttpCallback<T>
type HttpCallback<T> = (data: T, error?: HttpError) => void
A callback function that's going to be called every time the API call is resolved.
- Config:
config?: HttpConfig<T>
type HttpConfig<T> = {
headers: any,
timeout: number
}
Here you can add any custom configuration you want to your API call, or maybe something that alters the behavior of your hook.
- Return type
type HttpHook<T> = [(body?: any) => Promise<void>, {
loading: boolean,
data: T,
error: HttpError | null
}]
Our hook will return 2 values
- The actual HTTP request
- An object with the loading state, the error state, and the response data
The error type could be whatever you API returns. E.g:
export class HttpError {
status: number
message: string
constructor(status: number, message: string) {
this.status = status
this.message = message
}
}
Now let's go with the actual logic of our hook:
// Create some states for our
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<HttpError | null>(null)
async function fetchApi(body?: any): Promise<void> {
setLoading(true)
let err = null
let data: T = {} as T
try {
// Await for the request
const response: any = await request(body)
// This might vary based on your API
if(response.data) {
data = response.data;
} else {
data = response;
}
} catch (_err) {
err = _err
} finally {
// Set loading to false and verify the response status, if there's an error or whatever you need
setLoading(false)
if (err) {
if(cb) cb(data, err)
setError(err)
} else {
setResponse(data)
setError(null)
if(cb) cb(data)
}
}
}
return [fetchApi, {loading, data: response, error}]
This is the simplest logic for our hook, from this you can add your own configuration params, change the validation based on your API, you could also create a dataExtractor function if you need to use multiple APIs.
Now! Let's go with some examples.
First, create a data type and a dummy API call.
type LightsaberType = {
color: string,
material: string
}
let lightsabers: LightsaberType[] = [{
color: 'blue',
material: 'Electrum Plated'
}, {
color: 'red',
material: 'Durasteel'
}]
const fetchLightsaberList = async () => {
return lightsabers
}
And it's as simple as this
export const LightsaberListScreen = () => {
const [getLightsabers, { loading, data }] = useHttp<LightsaberType[]>(fetchLightsaberList)
useEffect(() => {
getLightsabers()
}, [])
return (
<View>
{loading? (
<ActivityIndicator />
) : (
<FlatList
data={data || []}
renderItem={({item}) => null}
/>
)}
</View>
)
}
But, what if you want to handle your own state...
export const LightsaberListScreen = () => {
const [lightsabers, setLightsabers] = useState<LightsaberType[]>([])
const [getLightsabers, { loading }] = useHttp<LightsaberType[]>(
fetchLightsaberList,
(data, err) => {
if(err) showCustomToast('Oh no, I don\'t have any lightsaber!')
setLightsabers(data)
}
)
useEffect(() => {
getLightsabers()
}, [])
return (
<View>
{loading? (
<ActivityIndicator />
) : (
<FlatList
data={lightsabers}
renderItem={({item}) => null}
/>
)}
</View>
)
}
OR you need to pass a body
const postLightsaber = async (lightsaber: LightsaberType) => {
lightsabers.push(lightsaber)
return lightsabers
}
...LightsaberListScreen component
const [createLightsaber] = useHttp<LightsaberType[]>(
(body: LightsaberType) => postLightsaber(body),
(data, err) => {
if(err) showCustomToast('Oh no, you\'ll need to go back to Ilum')
setLightsabers(data)
}
)
return (
<Button
text="Create your own lightsaber"
onPress={() => createLightsaber({
color: 'cian',
material:'Aurodium'
})}/>
)
This is my first article so I really hope you like it and I'm completely open to suggestions!
Happy coding π
Top comments (0)