A http library for React JS built on top of JS native fetch.
Contributing Guidelines · Changelog
Just follow links below to get an overview of library features.
- Contents
- Getting started
- Provider
- Http client
- Request hooks
- Http request hook params
- Http request hook return
- Http request state
- Example – Http request hook triggered automatically on component mount
- Example – Http request hook triggered manually on component mount
- Example – Non-abortable http request hook
- Example – Aborting http request triggered by the hook
- Example – Http post request hook
- Events
- Caching
- Browser support
Install the package by using npm
npm install react-http-fetch or yarn
yarn add react-http-fetch You can override the default configuration used by the http client to perform any request by using the HttpClientConfigProvider:
import React from 'react'; import { defaultHttpReqConfig, HttpClientConfigProvider } from 'react-http-fetch'; function Child() { return ( <div> Child component </div> ); }; function httpResponseParser(res) { return res.json(); } function App() { /** * Provided configs are automatically merged to the default one. */ const httpReqConfig = { // ...defaultHttpReqConfig, baseUrl: process.env.BACKEND_URL, responseParser: httpResponseParser, reqOptions: { headers: { 'Content-Type': 'text/html; charset=UTF-8', }, }, }; return ( <HttpClientConfigProvider config={httpReqConfig}> <Child /> </HttpClientConfigProvider> ); } export default App;Below the complete set of options you can provide to the HttpClientConfigProvider:
| Option | Description | Default |
|---|---|---|
| baseUrl | The base url used by the client to perform any http request (e.g. http://localhost:8000) | '' |
| responseParser | A function that maps the native fetch response. The default parser transform the fetch response stream into a json (https://developer.mozilla.org/en-US/docs/Web/API/Response/json) | httpResponseParser |
| requestBodySerializer | A function used to serialize request body. The default serializer take into account a wide range of data types to figure out which type of serialization to perform | serializeRequestBody |
| reqOptions | The default request option that will be carried by any request dispatched by the client. See HttpRequestOptions | { headers: { 'Content-Type': 'application/json' } } |
| cacheStore | The store for cached http responses. By default an in-memory cache store is used. | HttpInMemoryCacheStore |
| cacheStorePrefix | The prefix concatenated to any cached entry. | rfh |
| cacheStoreSeparator | Separates the store prefix and the cached entry identifier | __ |
The useHttpClient hook return a set of methods to perform http requests. The request function is the lowest level one, all other exposed functions are just decorators around it. Below a basic example using request:
import React from 'react'; import { useHttpClient } from 'react-http-fetch'; function App() { const { request } = useHttpClient(); const [todo, setTodo] = useState(); useEffect( () => { const fetchTodo = async () => { const res = await request({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', requestOptions: { method: 'GET', }, }); setTodo(res); }; fetchTodo(); }, [request] ); return ( <div>{`Todo name: ${todo && todo.title}`}</div> ); } export default App;The complete public API exposed by the hook:
| Method | Description | Params | Return |
|---|---|---|---|
| request | The lowest level method to perform a http request | Request params | Request return |
| Make use of lower level method request by just overriding the http method (example) | Request params | Request return |
| abortableRequest | The lowest level method to perform an abortable http request (example) | Request params | Abortable request return |
| Make use of lower level method abortableRequest by just overriding the http method | Request params | Abortable request return |
| Parameter | Type | Description |
|---|---|---|
| baseUrlOverride | string | The base url of the request. If provided, it would override the provider base url. |
| relativeUrl | string | The url relative to the base one (e.g. posts/1). |
| parser | HttpResponseParser | An optional response parser that would override the provider global one. |
| context | HttpContext | An optional context that carries arbitrary user defined data. See examples. |
| requestOptions | HttpRequestOptions | The options carried by the fetch request. |
The jsonified return value of native JS fetch. If a custom response parser (see Provider) is provided then the return value corresponds to the parsed one.
| Value | Type |
|---|---|
| [request, abortController] | [RequestReturn, AbortController] |
import React, { useState, useRef } from 'react'; import { useHttpClient } from 'react-http-fetch'; function App() { const { abortableRequest } = useHttpClient(); const abortCtrlRef = useRef(); const [todo, setTodo] = useState(); const fetchTodo = async () => { const [reqPromise, abortController] = abortableRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', }); abortCtrlRef.current = abortController; try { const res = await reqPromise; setTodo(res); } catch (error) { // Abort the request will cause the request promise to be rejected with the following error: // "DOMException: The user aborted a request." console.error(error); } finally { abortCtrlRef.current = undefined; } }; const abortPendingRequest = () => { if (abortCtrlRef.current) { abortCtrlRef.current.abort(); } }; return ( <div style={{ margin: '20px' }}> <div>{`Todo name: ${todo && todo.title}`}</div> <button style={{ marginRight: '10px' }} type="button" onClick={fetchTodo} > Do request </button> <button type="button" onClick={abortPendingRequest} > Abort </button> </div> ); } export default App;import React, { useState, useEffect } from 'react'; import { useHttpClient } from 'react-http-fetch'; function App() { const { get } = useHttpClient(); const [todo, setTodo] = useState(); useEffect( () => { const fetchTodo = async () => { const res = await get({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', }); setTodo(res); }; fetchTodo(); }, [get] ); return ( <div>{`Todo name: ${todo && todo.title}`}</div> ); } export default App;import React, { useEffect } from 'react'; import { useHttpClient, useHttpEvent, RequestStartedEvent, HttpContextToken, HttpContext, } from 'react-http-fetch'; const showGlobalLoader = new HttpContextToken(true); const reqContext = new HttpContext().set(showGlobalLoader, false); function App() { const { request } = useHttpClient(); useHttpEvent(RequestStartedEvent, (payload) => { console.log('Show global loader:', payload.context.get(showGlobalLoader)); }); useEffect( () => { const fetchTodo = async () => { await request({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', context: reqContext, }); }; fetchTodo(); }, [request] ); return ( <h1>Http Context</h1> ); } export default App;The library provides a hook useHttpRequest managing the state of the http request. Such state is returned by the hook along with a function to trigger the request. See params and return for more info. A dedicated hook is provided for every http method: useHttpGet, useHttpPost, useHttpPatch, useHttpPut, useHttpDelete.
| Parameter | Type | Description |
|---|---|---|
| baseUrlOverride | string | The base url of the request. If provided, it would override the provider base url. |
| relativeUrl | string | The url relative to the base one (e.g. posts/1). |
| parser | HttpResponseParser | An optional response parser that would override the provider global one. |
| context | HttpContext | An optional context that carries arbitrary user defined data. See examples. |
| requestOptions | HttpRequestOptions | The options carried by the fetch request. |
| initialData | any | The value that the state assumes initially before the request is send. |
| fetchOnBootstrap | boolean | Tell if the fetch must be triggered automatically when mounting the component or not. In the second case we would like to have a manual fetch, this is optained by a request function returned by the hook. |
Returns an array of three elements:
- The first one embeds the state of the http request.
- The second is a function that can be used to perform an abortable http request.
- The third is a function that can be used to perform a non-abortable http request.
See examples for further details. The table below describes the shape (i.e. properties) of http request state.
| Property | Type | Description |
|---|---|---|
| pristine | boolean | Tells if the request has been dispatched. |
| errored | boolean | Tells if the request has returned an error. |
| isLoading | boolean | Tells if the request is pending. |
| error | unknown | property evaluated by the error generated by the backend api. |
| data | any | The response provided by the backend api. |
import React from 'react'; import { useHttpRequest } from 'react-http-fetch'; function App() { const [state] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', requestOptions: {}, initialData: {}, fetchOnBootstrap: true, }); return ( <div>{`Todo name: ${(state.data && state.data.title) || 'unknown'}`}</div> ); } export default App;import { useHttpRequest } from 'react-http-fetch'; import React, { useEffect } from 'react'; function App() { const [state, request] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', }); useEffect(() => { const { reqResult, abortController } = request(); reqResult .then(res => console.log('request response', res)) .catch(err => console.error(err)); // You can use the returned AbortController instance to abort the request // abortController.abort(); }, [request]); return ( <div>{`Todo name: ${(state.data && state.data.title) || 'unknown'}`}</div> ); } export default App;Placeholder import React, { useEffect } from 'react'; import { useHttpRequest } from 'react-http-fetch'; function App() { const [state, , request] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', }); useEffect(() => request(), [request]); return ( <div>{`Todo name: ${(state.data && state.data.title) || 'unknown'}`}</div> ); } export default App;import { useHttpRequest } from 'react-http-fetch'; import React, { useRef } from 'react'; function App() { const [state, request] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', }); const abortCtrlRef = useRef(); const fetchTodo = () => { abortPendingRequest(); const { reqResult, abortController } = request(); abortCtrlRef.current = abortController; reqResult // Abort the request will cause the request promise to be rejected with the following error: // "DOMException: The user aborted a request." .catch(err => console.error(err)); }; const abortPendingRequest = () => { if (abortCtrlRef.current) { abortCtrlRef.current.abort(); } }; return ( <div style={{ margin: '20px' }}> <div>{`Todo name: ${(state.data && state.data.title) || 'unknown'}`}</div> <button style={{ marginRight: '10px' }} type="button" onClick={fetchTodo} > Do request </button> <button type="button" onClick={abortPendingRequest} > Abort </button> </div> ); } export default App;import React, { useState } from 'react'; import { useHttpPost } from 'react-http-fetch'; function App() { const [inputs, setInputs] = useState({}); const handleChange = (event) => { const name = event.target.name; const value = event.target.value; setInputs(values => ({...values, [name]: value})) } const [, createPostRequest] = useHttpPost({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'posts', }); const createPost = async (event) => { event.preventDefault(); const { postTitle, postBody } = inputs; const reqBody = { title: postTitle, body: postBody }; try { // Providing request options when running the request. // Provided options will be merged to the one provided // to the hook useHttpPost. await createPostRequest({ requestOptions: { body: reqBody } }); alert('Post created!'); } catch (error) { console.error(error); alert('An error occured. Check the browser console.'); } }; return ( <form onSubmit={createPost}> <label style={{ display: 'block' }}> Title: <input type="text" name="postTitle" value={inputs.postTitle || ""} onChange={handleChange} /> </label> <label style={{ display: 'block' }}> Body: <input type="text" name="postBody" value={inputs.postBody || ""} onChange={handleChange} /> </label> <button type="submit"> Create Post </button> </form> ); } export default App;Every time a request is executed the events shown below will be emitted. Each event carries a specific payload.
| Event type | Payload type |
|---|---|
| RequestStartedEvent | HttpRequest |
| RequestErroredEvent | HttpError |
| RequestSuccededEvent | RequestSuccededEventPayload |
You can subscribe a specific event using the useHttpEvent hook as shown below:
import { useState } from 'react'; import { RequestErroredEvent, RequestStartedEvent, RequestSuccededEvent, useHttpEvent, useHttpRequest } from 'react-http-fetch'; function App() { const [count, setCount] = useState(0); const [, request] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', }); useHttpEvent(RequestStartedEvent, () => setCount(count + 1)); useHttpEvent(RequestSuccededEvent, () => setCount(count > 0 ? count - 1 : 0)); useHttpEvent(RequestErroredEvent, () => setCount(count > 0 ? count - 1 : 0)); return ( <> <button onClick={request}>{'increment count:'}</button> <span>{count}</span> </> ); } export default App;Any request can be cached by setting the maxAge (expressed in milliseconds) parameter as part of the request options as shown below:
import { useHttpRequest } from 'react-http-fetch'; import React from 'react'; function App() { const [state, request] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', requestOptions: { maxAge: 60000 } // Cache for 1 minute }); const fetchTodo = () => { const { reqResult } = request(); reqResult.then(res => console.log(res)) }; return ( <> <div> {`Todo name: ${(state && state.data && state.data.title) || ''}`} </div> <button type="button" onClick={fetchTodo}> Make request </button> </> ); } export default App;By default the http client uses an in-memory cache, so it will be flushed everytime a full app refresh is performed. You can override the default caching strategy by providing your own cache store. The example below shows a http cache store based on session storage:
import React from 'react'; import { useHttpRequest, HttpClientConfigProvider } from 'react-http-fetch'; export class HttpSessionStorageCacheStore { /** * The local cache providing for a request identifier * the corresponding cached entry. */ _store = window.sessionStorage; /** * @inheritdoc */ get(identifier) { const stringifiedEntry = this._store.getItem(identifier); if (!stringifiedEntry) { return; } try { const parsedEntry = JSON.parse(stringifiedEntry); return parsedEntry; } catch (err) { return; } } /** * @inheritdoc */ put(identifier, entry) { try { const stringifiedEntry = JSON.stringify(entry); this._store.setItem(identifier, stringifiedEntry); return () => this.delete(identifier); } catch (err) { return () => {}; } } /** * @inheritdoc */ has(identifier) { return this._store.has(identifier); } /** * @inheritdoc */ delete(identifier) { this._store.removeItem(identifier); } /** * Gets all entry keys. */ _keys() { return Object.keys(this._store); } /** * Gets all stored entries. */ entries() { return this._keys() .map(entryKey => this._store.getItem(entryKey)); } /** * @inheritdoc */ flush() { this._keys().forEach((itemKey) => { this.delete(itemKey); }); } } const httpCacheStore = new HttpSessionStorageCacheStore(); function Child() { const [state, request] = useHttpRequest({ baseUrlOverride: 'https://jsonplaceholder.typicode.com', relativeUrl: 'todos/1', requestOptions: { maxAge: 60000 } // Cache for 1 minute }); console.log('Request state:', state.data); const fetchTodo = () => { const { reqResult } = request(); reqResult.then(res => console.log('Request response: ', res)) }; return ( <> <div> {`Todo name: ${(state && state.data && state.data.title) || ''}`} </div> <button type="button" onClick={fetchTodo}> Make request </button> </> ); }; function App() { const httpReqConfig = { cacheStore: httpCacheStore, // "prefix" and "separator" are not mandatory, // if not provided the default ones will be used. cacheStorePrefix: 'customPrefix', cacheStoreSeparator: '-' }; return ( <HttpClientConfigProvider config={httpReqConfig}> <Child /> </HttpClientConfigProvider> ); } export default App;![]() Edge | ![]() Firefox | ![]() Chrome | ![]() Safari |
|---|---|---|---|
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |



