Skip to content

A http library for React JS built on top of JS native fetch.

License

Notifications You must be signed in to change notification settings

nebarf/react-http-fetch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React http fetch

react-http-fetch logo
A http library for React JS built on top of JS native fetch.

Contributing Guidelines · Changelog

Build status   react-http-fetch on npm   MIT license



Contents

Just follow links below to get an overview of library features.


Getting started

Install the package by using npm

npm install react-http-fetch

or yarn

yarn add react-http-fetch

Provider

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 __

Http client

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;

Public API

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
  • get
  • post
  • put
  • patch
  • delete
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
  • abortableGet
  • abortablePost
  • abortablePut
  • abortablePatch
  • abortableDelete
Make use of lower level method abortableRequest by just overriding the http method Request params Abortable request return

Request params

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.

Request return

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.

Abortable request return

Value Type
[request, abortController] [RequestReturn, AbortController]

Example – Abortable request

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;

Example – Get request

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;

Example – Http context

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;

Request hooks

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.

Http request hook params

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.

Http request hook return

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.

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.

Example – Http request hook triggered automatically on component mount

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;

Example – Http request hook triggered manually on component mount

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;

Example – Non-abortable http request hook

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;

Example – Aborting http request triggered by the hook

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;

Example – Http post request hook

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;

Events

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;

Caching

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;

Browser support

Edge
Edge
Firefox
Firefox
Chrome
Chrome
Safari
Safari
last 2 versions last 2 versions last 2 versions last 2 versions