nebarf/react-http-fetch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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



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


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:

OptionDescriptionDefault
baseUrlThe base url used by the client to perform any http request (e.g. http://localhost:8000)''
responseParserA 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
requestBodySerializerA 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 performserializeRequestBody
reqOptionsThe default request option that will be carried by any request dised by the client. See HttpRequestOptions{ headers: { 'Content-Type': 'application/json' } }
cacheStoreThe store for cached http responses. By default an in-memory cache store is used.HttpInMemoryCacheStore
cacheStorePrefixThe prefix concatenated to any cached entry.rfh
cacheStoreSeparatorSeparates 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:

MethodDescriptionParamsReturn
requestThe lowest level method to perform a http requestRequest paramsRequest return
  • get
  • post
  • put
  • delete
Make use of lower level method request by just overriding the http method (example)Request paramsRequest return
abortableRequestThe lowest level method to perform an abortable http request (example)Request paramsAbortable request return
  • abortableGet
  • abortablePost
  • abortablePut
  • abortable
  • abortableDelete
Make use of lower level method abortableRequest by just overriding the http methodRequest paramsAbortable request return
ParameterTypeDescription
baseUrlOverridestringThe base url of the request. If provided, it would override the provider base url.
relativeUrlstringThe url relative to the base one (e.g. posts/1).
parserHttpResponseParserAn optional response parser that would override the provider global one.
contextHttpContextAn optional context that carries arbitrary user defined data. See examples.
requestOptionsHttpRequestOptionsThe 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.

ValueType
[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, useHttp, useHttpPut, useHttpDelete.

ParameterTypeDescription
baseUrlOverridestringThe base url of the request. If provided, it would override the provider base url.
relativeUrlstringThe url relative to the base one (e.g. posts/1).
parserHttpResponseParserAn optional response parser that would override the provider global one.
contextHttpContextAn optional context that carries arbitrary user defined data. See examples.
requestOptionsHttpRequestOptionsThe options carried by the fetch request.
initialDataanyThe value that the state assumes initially before the request is send.
fetchOnBootstrapbooleanTell 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.

PropertyTypeDescription
pristinebooleanTells if the request has been dised.
erroredbooleanTells if the request has returned an error.
isLoadingbooleanTells if the request is pending.
errorunknownproperty evaluated by the error generated by the backend api.
dataanyThe 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 typePayload type
RequestStartedEventHttpRequest
RequestErroredEventHttpError
RequestSuccededEventRequestSuccededEventPayload

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
Edge
Firefox
Firefox
Chrome
Chrome
Safari
Safari
last 2 versionslast 2 versionslast 2 versionslast 2 versions

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •