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 |