
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:
importReactfrom'react';import{defaultHttpReqConfig,HttpClientConfigProvider}from'react-http-fetch';functionChild(){return(<div> Child component </div>);};functionhttpResponseParser(res){returnres.json();}functionApp(){/** * Provided configs are automatically merged to the default one. */consthttpReqConfig={// ...defaultHttpReqConfig,baseUrl: process.env.BACKEND_URL,responseParser: httpResponseParser,reqOptions: {headers: {'Content-Type': 'text/html; charset=UTF-8',},},};return(<HttpClientConfigProviderconfig={httpReqConfig}><Child/></HttpClientConfigProvider>);}exportdefaultApp;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:
importReactfrom'react';import{useHttpClient}from'react-http-fetch';functionApp(){const{ request }=useHttpClient();const[todo,setTodo]=useState();useEffect(()=>{constfetchTodo=async()=>{constres=awaitrequest({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'todos/1',requestOptions: {method: 'GET',},});setTodo(res);};fetchTodo();},[request]);return(<div>{`Todo name: ${todo&&todo.title}`}</div>);}exportdefaultApp;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] |
importReact,{useState,useRef}from'react';import{useHttpClient}from'react-http-fetch';functionApp(){const{ abortableRequest }=useHttpClient();constabortCtrlRef=useRef();const[todo,setTodo]=useState();constfetchTodo=async()=>{const[reqPromise,abortController]=abortableRequest({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'todos/1',});abortCtrlRef.current=abortController;try{constres=awaitreqPromise;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;}};constabortPendingRequest=()=>{if(abortCtrlRef.current){abortCtrlRef.current.abort();}};return(<divstyle={{margin: '20px'}}><div>{`Todo name: ${todo&&todo.title}`}</div><buttonstyle={{marginRight: '10px'}}type="button"onClick={fetchTodo}>
Do request
</button><buttontype="button"onClick={abortPendingRequest}>
Abort
</button></div>);}exportdefaultApp;importReact,{useState,useEffect}from'react';import{useHttpClient}from'react-http-fetch';functionApp(){const{ get }=useHttpClient();const[todo,setTodo]=useState();useEffect(()=>{constfetchTodo=async()=>{constres=awaitget({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'todos/1',});setTodo(res);};fetchTodo();},[get]);return(<div>{`Todo name: ${todo&&todo.title}`}</div>);}exportdefaultApp;importReact,{useEffect}from'react';import{useHttpClient,useHttpEvent,RequestStartedEvent,HttpContextToken,HttpContext,}from'react-http-fetch';constshowGlobalLoader=newHttpContextToken(true);constreqContext=newHttpContext().set(showGlobalLoader,false);functionApp(){const{ request }=useHttpClient();useHttpEvent(RequestStartedEvent,(payload)=>{console.log('Show global loader:',payload.context.get(showGlobalLoader));});useEffect(()=>{constfetchTodo=async()=>{awaitrequest({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'todos/1',context: reqContext,});};fetchTodo();},[request]);return(<h1>Http Context</h1>);}exportdefaultApp;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. |
importReactfrom'react';import{useHttpRequest}from'react-http-fetch';functionApp(){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>);}exportdefaultApp;import{useHttpRequest}from'react-http-fetch';importReact,{useEffect}from'react';functionApp(){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>);}exportdefaultApp;PlaceholderimportReact,{useEffect}from'react';import{useHttpRequest}from'react-http-fetch';functionApp(){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>);}exportdefaultApp;import{useHttpRequest}from'react-http-fetch';importReact,{useRef}from'react';functionApp(){const[state,request]=useHttpRequest({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'todos/1',});constabortCtrlRef=useRef();constfetchTodo=()=>{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));};constabortPendingRequest=()=>{if(abortCtrlRef.current){abortCtrlRef.current.abort();}};return(<divstyle={{margin: '20px'}}><div>{`Todo name: ${(state.data&&state.data.title)||'unknown'}`}</div><buttonstyle={{marginRight: '10px'}}type="button"onClick={fetchTodo}>
Do request
</button><buttontype="button"onClick={abortPendingRequest}>
Abort
</button></div>);}exportdefaultApp;importReact,{useState}from'react';import{useHttpPost}from'react-http-fetch';functionApp(){const[inputs,setInputs]=useState({});consthandleChange=(event)=>{constname=event.target.name;constvalue=event.target.value;setInputs(values=>({...values,[name]: value}))}const[,createPostRequest]=useHttpPost({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'posts',});constcreatePost=async(event)=>{event.preventDefault();const{ postTitle, postBody }=inputs;constreqBody={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.awaitcreatePostRequest({requestOptions: {body: reqBody}});alert('Post created!');}catch(error){console.error(error);alert('An error occured. Check the browser console.');}};return(<formonSubmit={createPost}><labelstyle={{display: 'block'}}>
Title:
<inputtype="text"name="postTitle"value={inputs.postTitle||""}onChange={handleChange}/></label><labelstyle={{display: 'block'}}>
Body:
<inputtype="text"name="postBody"value={inputs.postBody||""}onChange={handleChange}/></label><buttontype="submit">
Create Post
</button></form>);}exportdefaultApp;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';functionApp(){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(<><buttononClick={request}>{'increment count:'}</button><span>{count}</span></>);}exportdefaultApp;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';importReactfrom'react';functionApp(){const[state,request]=useHttpRequest({baseUrlOverride: 'https://jsonplaceholder.typicode.com',relativeUrl: 'todos/1',requestOptions: {maxAge: 60000}// Cache for 1 minute});constfetchTodo=()=>{const{ reqResult }=request();reqResult.then(res=>console.log(res))};return(<><div>{`Todo name: ${(state&&state.data&&state.data.title)||''}`}</div><buttontype="button"onClick={fetchTodo}>
Make request
</button></>);}exportdefaultApp;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:
importReactfrom'react';import{useHttpRequest,HttpClientConfigProvider}from'react-http-fetch';exportclassHttpSessionStorageCacheStore{/** * The local cache providing for a request identifier * the corresponding cached entry. */_store=window.sessionStorage;/** * @inheritdoc */get(identifier){conststringifiedEntry=this._store.getItem(identifier);if(!stringifiedEntry){return;}try{constparsedEntry=JSON.parse(stringifiedEntry);returnparsedEntry;}catch(err){return;}}/** * @inheritdoc */put(identifier,entry){try{conststringifiedEntry=JSON.stringify(entry);this._store.setItem(identifier,stringifiedEntry);return()=>this.delete(identifier);}catch(err){return()=>{};}}/** * @inheritdoc */has(identifier){returnthis._store.has(identifier);}/** * @inheritdoc */delete(identifier){this._store.removeItem(identifier);}/** * Gets all entry keys. */_keys(){returnObject.keys(this._store);}/** * Gets all stored entries. */entries(){returnthis._keys().map(entryKey=>this._store.getItem(entryKey));}/** * @inheritdoc */flush(){this._keys().forEach((itemKey)=>{this.delete(itemKey);});}}consthttpCacheStore=newHttpSessionStorageCacheStore();functionChild(){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);constfetchTodo=()=>{const{ reqResult }=request();reqResult.then(res=>console.log('Request response: ',res))};return(<><div>{`Todo name: ${(state&&state.data&&state.data.title)||''}`}</div><buttontype="button"onClick={fetchTodo}>
Make request
</button></>);};functionApp(){consthttpReqConfig={cacheStore: httpCacheStore,// "prefix" and "separator" are not mandatory,// if not provided the default ones will be used.cacheStorePrefix: 'customPrefix',cacheStoreSeparator: '-'};return(<HttpClientConfigProviderconfig={httpReqConfig}><Child/></HttpClientConfigProvider>);}exportdefaultApp;![]() Edge | ![]() Firefox | ![]() Chrome | ![]() Safari |
|---|---|---|---|
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |



