npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@pinkairship/use-data-fetch

v3.3.5

Published

A data fetch hook that stays out of your way.

Downloads

945

Readme

useDataFetch

A data fetch hook that stays out of your way.

Several react hooks exist that allow you to fetch data from a server, but most of them do too much for you. This library takes the best part of the fetch hooks (consistent access, global config, easy use, etc.) and makes it as simple as possible.

This library is also accessibility friendly, allowing for easy setup to alert screenreaders when data is fetched. For more information on screenreaders, see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions.

Table of Contents

Table of Contents created by gh-md-toc

Install

With npm

npm install @pinkairship/useDataFetch

With yarn

yarn add @pinkairship/useDataFetch

Usage

Wrap the tree you wish to add and remove messages with a DataFetchProvider:

function App() {
  return <DataFetchProvider>// children here</DataFetchProvider>
}

Then create a component that will hook into using the data fetch instance:

export function MakeGet({
  show = ({ data }) => alert(`User Id: ${data.user.id}`),
}) {
  const { get } = useDataFetch('/userinfo')

  return (
    <div>
      <input
        type="button"
        onClick={() => get().then(show)}
        value="Make Get"
      />
    </div>
  )
}

This library defines multiple hooks that build on each other and use the DataFetchProvider. They are defined below.

useDataFetch

useDataFetch is the basic building block hook of this library. The basic usage of useDataFetch:

import React, { useState } from 'react'

import { DataFetchProvider, useDataFetch } from '../src'

function App () {
  return (
    <DataFetchProvider>
      <MakeGet />
    </DataFetchProvider>
  )
}

function MakeGet () {
  const { get } = useDataFetch('http://localhost/userinfo')

  return (
    <div>
      <input
        type="button"
        onClick={() => get().then(({data}) => alert(`User Id: ${data.user.id}`)}) }
        value="Make Get"
      />
    </div>
  )
}

See example/App.js for more examples of how to use the other type of http requests (POST, PUT, DELETE, PATCH, and a custom config).

useFetched

useFetched is a hook that acts like useState but fetches the initial value from the server (if path provided), or allows you to create a value using the familiar useState functionality that will then provide functions to post, patch, etc the data to the server. The basic usage is as follows:

function UseManagedFetch() {
  const [id, setId, , dataFetch] = useFetched('/echo/myId')

  return (
    <div>
      <input
        type="button"
        onClick={() =>
          setId({
            id: nanoid(),
            data: 'I am created without a trip to the server!',
          })
        }
        value="Make Managed State Update without Server Trip"
      />
      <input
        type="button"
        onClick={() => dataFetch.post()}
        value="Save to server as new entity"
      />
      <input
        type="button"
        onClick={() => dataFetch.put()}
        value="Update entity after it has been created"
      />
      <div>{requestState}</div>
      <div>{id.data}</div>
    </div>
  )
}

See example/App.js for examples on how to use the other functions from datafetch.

useFetchedArray

useFetchedArray is a hook that acts like useState but fetches the initial collection of values from a server. It supports the full lifecycle of items in a collection. The basic usage is as follows:

function UseManagedArrayFetch() {
  const [ids, setIds, requestState, dataFetch] = useFetchedArray(
    '/randomIds'
  )

  return (
    <div>
      <input
        type="button"
        onClick={() => dataFetch.post()}
        value="Make Managed Array State Post"
      />

      ... (other calls from dataFetch)

      <div>{requestState}</div>
      {ids.map((id) => (
        <div key={id.id}>{id.data}</div>
      ))}
    </div>
  )
}

See example/App.js for examples on how to use the other functions from datafetch.

useFetchOnMount

useFetchOnMount handles the common use case of loading data on mount. Without useFetchOnMount, when fetching data on mount of a component is handled as follows:

function MakeGet () {
  const updateStatefunc = updateStateFunc(somestate)
  const { get } = useDataFetch('http://localhost/userinfo')

  useEffect(() => get.then(updateStateFunc).catch(onErrorHandler)))

  ...
}

With preload:

function MakeGet () {
  const updateStatefunc = updateStateFunc(somestate)
  const { get } = useDataFetch('http://localhost/userinfo', {
    hookOptions: {
      updateStateHook: updateStateFunc
    }
  })

  const refetch = () => get()

  ...
}

The advantages of this api:

  1. Simplified onMount datafetch management
  2. Greater control over refetching
  3. More delcaritive naming of onMount fetching

All of the options used for the base useDataFetch can be passed down in the hookOptions section (and all options defined at the same levels as before are handled the same way).

See example/App.js for more a more complete example.

Additional Usage Information

There are a few features worth calling out specifically in the usage section.

Caching data

Caching data is useful if you have several components in your application that use the same data but it is inconvenient to pass that data using props. Setting a cache behavior allows you to setup a datafetch for an endpoint and then retrieve that same data only once for multiple components.

This behavior is used in other libraries like react-relay and apollo where graphql calls are stored, except that the caching algorithm of this cache is just a simple last-recently-used cache and does not attempt to make any assumptions about your data and how to cache it.

Caching Definition and Precedence Order

Caching can be set in three separate places (described in precedence order):

  1. At the get|post|put|patch|... level
  2. At the useDataFetch level
  3. At the DataFetchProvider level

In other words, the caching level set at DataFetchProvider is overridden by the caching level set at useDataFetch, which in turn is overridden by the caching level set at the actual request call.

How to Use Cached Calls

To use cached calls, you can set the value at any of the level described and the cache will return the stored value for any call made to a url with the same path.

import React, { useState } from 'react'

import { DataFetchProvider, useDataFetch } from '../src'

function App () {
  return (
    <DataFetchProvider useCache={true}>
      <MakeGet />
      <MakeOtherGet />
      <MakeUncachedGet />
    </DataFetchProvider>
  )
}

// Makes a request to the server each time the button is pressed and will return
// a new randomId
function MakeUncachedGet () {
  const { get } = useDataFetch('http://localhost/randomId', { useCache: false })

  return (
    <div>
      <input
        type="button"
        onClick={() => get().then(({data}) => alert(`User Id: ${data.user.id}`)}) }
        value="Make Get"
      />
    </div>
  )
}

// A call made in either of these components will return the same user id even though
// the randomId is called in both
function MakeGet () {
  const { get } = useDataFetch('http://localhost/randomId')

  return (
    <div>
      <input
        type="button"
        onClick={() => get().then(({data}) => alert(`User Id: ${data.user.id}`)}) }
        value="Make Get"
      />
    </div>
  )
}
function MakeOtherGet () {
  const { get } = useDataFetch('http://localhost/randomId')

  return (
    <div>
      <input
        type="button"
        onClick={() => get().then(({data}) => alert(`User Id: ${data.user.id}`)}) }
        value="Make Other Get"
      />
    </div>
  )
}

Storing fetched data in state

  • Note that this is primarily a concern of useDataFetch and useFetchOnMount as the other hooks store the state for you internally (using this strategy).

To store fetched data you will need to pass a configuration object to useDataFetch that updates the state.

function MakeStoredGetFetch() {
  // set state on the component using useDataFetch
  const [ids, setIds] = useState([])
  // to prevent refetching data on each rerender, you must wrap the
  // the state update in a useCallback hook
  const updateStateHook = useCallback(
    // make sure to wrap the set state function in something that
    // will be called after the data is retrieved
    ({ data: id }) => setIds([...ids, id]),
    [ids]
  )
  const { get } = useDataFetch('/randomId', { updateStateHook })

  return (
    <div>
      <input
        type="button"
        onClick={() => get()}
        value="Make Stored Get"
      />
      {ids.map((id) => (
        <div key={id.id}>Created id: {id.id}</div>
      ))}
    </div>
  )
}

Screen Reader Alerts

To add screen reader alerts (which you should - read more here) pass in a function that accepts the message for the alert.

function MakeGetWithSrAlert() {
  const { get } = useDataFetch('/userinfo', {
    alertScreenReaderWith: 'Messages Came',
  })

  return (
    <div>
      <input
        type="button"
        onClick={() => get()}
        value="Make Get And Alert Screen Reader"
      />
    </div>
  )
}

Listening for Request State Changes

Many libraries will provide mechanisms to automatically detect the state that the request is in - retrieving data, was it a success, was it a failure. These state changes are useful to control the ui when fetching data.

You can listen to the request lifecycle of useDataFetch by defining the requestStateListener function at either the hook level or the request level. These functions will pass a string only that defines the state (one of running|success(for success)|error(for error)).

function MakeGet() {
  const [loading, setLoading] = useState('pending')
  const { get } = useDataFetch('/userinfo', {
    requestStateListener: setLoading,
  })

  return (
    <div>
      <input
        type="button"
        onClick={() => get()}
        value="Make Get - Success"
      />
      <div>Request State: {loading}</div>
    </div>
  )
}

API

The api for useDataFetch is pretty small intentionally - it isn't supposed to handle all use cases. If you want something that does more state management or handles automatic retries and caching behavior, this library may not be for you.

Definitions

There are is a need to define the levels that this document described. Often a setting set at one level can be overridden at a more granular level. The levels are as follows:

  1. Provider level - this is the first level and defines the configuration set when making a DataFetchProvider
  2. Hook Level - this is the second level and defines the configuration set when using the useDataFetch hook. Settings here override the Provider level.
  3. Request Level - this is the third level and defines the configuration set when making a request, ie get(). Settings here override the Provider and the Hook levels.

DataFetchProvider

To use the DataFetchProvider, do the following:

import { DataFetchProvider } from '@pinkairship/useDataFetch'

// Wrap the components that your mount point is going to use
function App() {
  return <DataFetchProvider>// children here</DataFetchProvider>
}

Below is table of the props that can be passed into the provider:

| Prop | Type | Description | | ---- | ---- | ----------- | | datafetchInstance | object | The instance of the http fetch object to be used.This must conform to the axios request api (https://github.com/axios/axios#axios-api).If not provided will use an axios instance. | | axiosCreateOpts | object | An object that conforms to the axios configuration api to be used when creating the dataFetchInstance. See https://github.com/axios/axios#request-config for more information. | | screenReaderAlert |function | The function your app uses to alert screenreaders. The function will be passed the value of alertScreenReaderWith.For more information, visit https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions. | | makeMockDataFetchInstance | function | A function that is used to wrap the dataFetchInstance calls for testing purposes. For example, the example app uses https://github.com/ctimmerm/axios-mock-adapter. | | useCache | boolean | Use a cache for all calls made with this provider. Defaults to false. Can be overridden when defining the request methods, and also when making a request. | | cacheSize | number | Set the number of items to keep track of before ejecting values from the cache. Defaults to 50. Cache is a last-recently-used cache. | | updateStateHook | function | A function that updates the state you wish to house your fetched data. This function will be passed the responseData and the requestConfig - (responseData, requestConfig) => {}. This hook is overridden if useDataFetch also defines updateStateHook.See the <AppThird> component in example/App.js for example of how it can be used. | | debugCache | boolean | Attaches an array to window with key dataFetchCaches. Use this to see what value was is in the cache. |

useDataFetch

To use the useDataFetch hook, do the following:

import { useDataFetch } from '@pinkairship/useDataFetch'

function MakeGet() {
  // destructure the get function to request info from /userinfo
  const { get } = useDataFetch('/userinfo')

  return (
    <div>
      <input
        type="button"
        // call the get function on click
        onClick={() => get()}
        value="Make Get"
      />
    </div>
  )
}

Note that get does not make a request automatically when useDataFetch is called. This is intentional and breaks from the pattern of many other hooks that wrap data fetching. You will need to call your get in a useEffect if you desire to have it fire on component load.

import { useEffect } from 'react'
import { useDataFetch } from '@pinkairship/useDataFetch'

function MakeGet() {
  // destructure the get function to request info from /userinfo
  const { get } = useDataFetch('/userinfo')
  useEffect(() => {
    get()
  })

  return (
    <div>
      <input
        type="button"
        // call the get function on click
        onClick={() => get()}
        value="Make Get"
      />
    </div>
  )
}

Below is a table of the parameters for useDataFetch:

| Parameter | Type | Description | | --------- | ---- | ----------- | | path | string | The path to your resource.This can be a fully qualified url, or just the path instance if you configured your DataFetchProvider to use a baseUrl (see https://github.com/axios/axios#request-config for more information on the axios api). | | config | object | An object that accepts specific values. |

The config parameter has a shape as follows:

| Configuration Key | Type | Description | | ----------------- | ---- | ----------- | | updateStateHook | function | A function that updates the state you wish to house your fetched data.This will override the DataFetchProvider if it has defined the updateStateHook. | | alertsScreenReaderWith | any | A message for the screenReaderAlert to read (see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions for more information).Note that your screenReaderAlert function can accept any type, so this also can be any type. | | requestConfig | object | An axios request configuration object (see https://github.com/axios/axios#request-config for more information on the axios api). | | useCache | boolean | Use the cache for all calls returned.Overrides the cache settings for the provider. Can be overridden by at the level that the call is made. | | requestStateListener | function | Listen for the state changes in the request objectThis is a function that recieves a string dictating the state (one of running|success(for success)|error(for error))Can be overridden at the request level. |

useDataFetch methods

useDataFetch will return an object with functions ready to make your api requests.

Available methods that useDataFetch will generate are:

| Method | Description | Usage | Notes | | --- | ---- | -------- | -------- | | get | make a get request | get() | Unlike axios, this does accept a data body get(data, opts = {}).Note that undefined must be passed in to use the requestConfig without passing in data. To send query params: get(undefined, { requestConfig: { params: {} } }). | | query | make a query (GET) request |query(params, opts = {}) | This is a nicer way of doing get(undefined, { requestConfig: { params: {} } }). | | post | make a post request | post(data, opts = {}) | | | put | make a put request |put(data, opts = {}) | | | patch | make a patch request |patch(data, opts = {}) | | | destroy | make a delete request| destroy(data, opts = {}) | | | request | make a custom request |request(data, opts = {}) | Note that this requires that you create a request config in your useDataFetch hook setup: useDataFetch(undefined, { requestConfig: <dataobject>})undefined (or some value) must be passed in first or else the requestConfig will not be registered and it will throw an error.For more infomration the axios request config, see https://github.com/axios/axios#request-config. You must include a url and a method in the requestConfig or an error will be thrown.|

All of these methods return an axios request promise if you do not replace the http library with something else. This allows you control to chain after a request.

Request Level Options

The request level allows you to dynamically change a few of the options by defining behavior on the fly. These options override similar options defined at the hook level or the provider level.

| Option | Description | | ------ | ----------- | | useCache | Use the cache for all calls returned | | requestConfig | Used to send any last minute configuration, such as dymanically generated query params - { params: { id: '1234' } }. | | requestStateListener | Listen for the state changes in the request object. This is a function that recieves a string dictating the state (one of running|success(for success)|error(for error)). |

useFetched

To use useFetched, do the following (note that this still requires the DataFetchProvider wrapping the component at some higher level):

function UseManagedFetch() {
  const [id, setId, requestState, dataFetch] = useFetched('/randomId')

  return (
    <div>
      <input
        type="button"
        onClick={() => dataFetch.post()}
        value="Make Managed State Post"
      />

      ... other datafetch requests

      <div>{requestState}</div>
      <div>{id.data}</div>
    </div>
  )
}

You can also use this hook like a regular use state that has a direct hook to create via the given endpoint once it has been created:

function UseManagedFetch() {
  const [id, setId, , dataFetch] = useFetched('/randomId')

  return (
    <div>
      <input
        type="button"
        onClick={() =>
          setId({
            id: nanoid(),
            data: 'I am created without a trip to the server!',
          })
        }
        value="Make Managed State Update without Server Trip"
      />
      <input
        type="button"
        onClick={() => dataFetch.post()}
        value="Save to server as new entity"
      />
      <input
        type="button"
        onClick={() => dataFetch.put()}
        value="Update entity after it has been created"
      />
      <div>{requestState}</div>
      <div>{id.data}</div>
    </div>
  )
}

Note that the pattern for updating assumes that you use POST to create a new entity from the route provided without the trailing id (ie /echo/myId would have a post path of /echo). There is a way around this as described below by using createUsesPath in the configuration.

The following table describes the parameters passed into the hook:

| Parameter | Type | Description | | --------- | ---- | ----------- | | path | string | The path to your resource.This can be a fully qualified url, or just the path instance if you configured your DataFetchProvider to use a baseUrl (see https://github.com/axios/axios#request-config for more information on the axios api). | | config | object | An object that accepts specific values. |

The config parameter has a shape as follows:

| Configuration Key | Type | Description | | ----------------- | ---- | ----------- | | onSuccess | func | A callback to fire when the request is successful. Note that this is called after the updateStateHook is called (if defined) | | onFailure | func | A callback to fire when the request is a failure. Fires after updateStateHook | | hookOptions | object | The options for the underlying useDataFetch function. See the useDataFetch config options for more details. | | createUsesPath | func | If your endpoints do not follow the convention described, you can pass in a function that will update the path to what it needs to be (ie (path) => /my/new/path) | | cancelRequestOnUnmount | boolean | Cancels your fetch request if the react component is unmounted. False by default. |

useFetchedArray

To use useFetchedArray, do the following (note that this still requires the DataFetchProvider wrapping the component at some higher level):

function UseManagedArrayFetch() {
  const [ids, setIds, requestState, dataFetch] = useFetchedArray(
    '/randomIds'
  )

  return (
    <div>
      <input
        type="button"
        onClick={() => dataFetch.post()}
        value="Make Managed Array State Post"
      />

      ... (other calls from dataFetch)

      <div>{requestState}</div>
      {ids.map((id) => (
        <div key={id.id}>{id.data}</div>
      ))}
    </div>
  )
}

You can also add values to the array directly that are not persisted:

function UseManagedArrayFetch() {
  const [ids, setIds, requestState, dataFetch] = useFetchedArray(
    '/randomIds'
  )

  return (
    <div>
      <input
        type="button"
        onClick={() =>
          setIds([
            ...ids,
            {
              id: nanoid(),
              data: 'I am created without a trip to the server!',
            },
          ])
        }
        value="Make Managed Array State Update without Server Trip"
      />

      ... (other calls from dataFetch)

      <div>{requestState}</div>
      {ids.map((id) => (
        <div key={id.id}>{id.data}</div>
      ))}
    </div>
  )
}

Caveats

useFetchedArray makes a few assumptions about your data, namely that each item in your collection is an object and that it contains a field called id. It also assumes that you are using restful endpoints similar to the Ruby on Rails convention of storing resource ids in the path (ie /posts is the collection endpoint and /posts/:id is a specific resource in the collection).

These caveats can be overcome by passing in a few configuration functions:

| Configuration Key | Description | | ---------------- | ----------- | | transform | use this format data such that all values are an object and there is an id field present (and to transform any way you want). Note that you can also do this with axios (See https://github.com/axios/axios#request-config and the transformData configuration section for more information) | | updatesUsePath | If your endpoints do not follow the convention described, you can pass in a function that will update the path to what it needs to be (ie if it stays the same, the function would be (path) => path) | | extractObjectKey | If you have a key but it is not called id, you can pass in a function that will extract the correct key. ie. (data) => data.myKey | | replaceValue | If you have data that doesn't match the shape described (ie objects that have an id field) you can pass in your own replaceValue function when you do an update call.This will be called only on (PUT|PATCH)Example: ({data}) => { ...getdata; setValues(newData) // setValues output from hook def} | | removeValue | Similar to replaceValue except it is used to remove the value on destroy only. See replaceValue for more details. |

Also, only get, post, put, patch, destroy are defined in the dataFetch object. If you require the flexibility provided by the full useDataFetch api, then this hook should not be used and useDataFetch should be used instead.

The destroy function is slightly changed - it has the following call signature: destroy(destroyData, removalKeys, opts). destroyData is the same as useDataFetch, removalKeys is the value (or values) of the id of the object that is being remove, and opts is the configuration opts for the underlying hook. If removalKeys is an array, then each value in the array is removed from the state.

Example destroy call:

// do not send any data, just remove this id
destroy(undefined, 'myId', {} //this last option can also be ommitted)

// send data and remove this id
destroy({id: 'asd'}, 'foo')

// destroy multiple at once
destroy([{id: 'asd'}, {id: 'bsd'}], ['asd', 'foo'])

API

The following table describes the parameters passed into the hook:

| Parameter | Type | Description | | --------- | ---- | ----------- | | path | string | The path to your resource.This can be a fully qualified url, or just the path instance if you configured your DataFetchProvider to use a baseUrl (see https://github.com/axios/axios#request-config for more information on the axios api). | | config | object | An object that accepts specific values. |

The config parameter has a shape as follows:

| Configuration Key | Type | Description | | ----------------- | ---- | ----------- | | onSuccess | func | A callback to fire when the request is successful. Note that this is called after the updateStateHook is called (if defined) | | onFailure | func | A callback to fire when the request is a failure. Fires after updateStateHook | | hookOptions | object | The options for the underlying useDataFetch function. See the useDataFetch config options for more details. | | transform | func | use this format data such that all values are an object and there is an id field present (and to transform any way you want). Note that you can also do this with axios (See https://github.com/axios/axios#request-config and the transformData configuration section for more information). | | updatesUsePath | func | If your endpoints do not follow the convention described, you can pass in a function that will update the path to what it needs to be (ie if it stays the same, the function would be (path) => path) | | extractObjectKey | func | If you have a key but it is not called id, you can pass in a function that will extract the correct key. ie. (data) => data.myKey | | replaceValue | func |If you have data that doesn't match the shape described (ie objects that have an id field) you can pass in your own replaceValue function when you do an update call.This will be called only on (PUT|PATCH)Example: ({data}) => { ...getdata; setValues(newData) // setValues output from hook def} | | removeValue | func | Similar to replaceValue except it is used to remove the value on destroy only. See replaceValue for more details. | | extractList | func | This hook will extract data from an api get call if that data returned is a list or an object with a single root key whose value is a list. Other returned data will need to be transformed. [{"id": ..}] is acceptable{"root":[{"id":...}]} is acceptable{"root":[{"id":...}], "otherKey":...} is NOT acceptable. You must define this function youself. | | cancelRequestOnUnmount | boolean | Cancels your fetch request if the react component is unmounted. False by default. |

useFetchOnMount

To use useFetchOnMount, do the following (note that this still requires the DataFetchProvider wrapping the component at some higher level):

import { useFetchOnMount } from '@pinkairship/useDataFetch'

function MakeGet() {
  // destructure the get function to request info from /userinfo
  const { get } = useFetchOnMount('/userinfo')

  return (
    <div>
      <input
        type="button"
        // call the get function on click
        onClick={() => get()}
        value="Refetch Get"
      />
    </div>
  )
}

If you want to track the request data using some state tracking:

import { useFetchOnMount } from '@pinkairship/useDataFetch'

function MakeGet() {
  // set state on the component using useDataFetch
  const [ids, setIds] = useState([])
  // to prevent refetching data on each rerender, you must wrap the
  // the state update in a useCallback hook
  const updateStateHook = useCallback(
    // make sure to wrap the set state function in something that
    // will be called after the data is retrieved
    ({ data: id }) => setIds([...ids, id]),
    [ids]
  )
  // destructure the get function to request info from /userinfo
  const { get } = useFetchOnMount('/userinfo', { hookOptions: updateStateHook })

  return (
    <div>
      <input
        type="button"
        // call the get function on click
        onClick={() => get()}
        value="Make Get"
      />
    </div>
  )
}

useFetchOnMount returns the same object as useDataFetch, so all the same api applies here except that you will need to pass those options through the hookOptions key of the opts config. There are a few additional options that can be used when defining a useFetchOnMount request.

| Parameter | Type | Description | | --------- | ---- | ----------- | | path | string | The path to your resource.This can be a fully qualified url, or just the path instance if you configured your DataFetchProvider to use a baseUrl (see https://github.com/axios/axios#request-config for more information on the axios api). | | config | object | An object that accepts specific values. |

The config parameter has a shape as follows:

| Configuration Key | Type | Description | | ----------------- | ---- | ----------- | | onSuccess | func | A callback to fire when the request is successful. Note that this is called after the updateStateHook is called (if defined) | | onFailure | func | A callback to fire when the request is a failure. Fires after updateStateHook | | hookOptions | object | The options for the underlying useDataFetch function. See the useDataFetch config options for more details. | | cancelRequestOnUnmount | boolean | Cancels your fetch request if the react component is unmounted. False by default. |

Testing Your Application with useDataFetch

For the most part testing your application while using useDataFetch should be pretty straightforward - wrap whatever component you are using useDataFetch with a DataFetchProvider instance.

function makeMockAxios = () => {... creates a mock axios instance ...}

function renderComponent(children) {
  return render( // some render function for your testing library
    <DataFetchProvider makeMockDataFetchInstance={makeMockAxios}>
      {children}
    </DataFetchProvider>
  )
}

test('my test', () => {
  const component = renderComponent(<MyComponent />)
  ... your test
})

You can check out examples of testing in the __tests__ folder for more information.

Spying Data Fetching

Sometimes you want to verify that a component makes a call with the correct data to your provider. This can be difficult with https://github.com/ctimmerm/axios-mock-adapter (the recommended mock library for axios).

In order to spy on a request, one approach is to create a mock that accepts a spy as the dataFetchInstance of the DataFetchProvider.

function renderComponent(children, dataFetchProps = {}) {
  return render( // some render function for your testing library
    <DataFetchProvider {...dataFetchProps}>
      {children}
    </DataFetchProvider>
  )
}

test('creates a new thing', async () => {
  let data
  const dataFetchInstance = (requestData) => {
    data = requestData
    return Promise.resolve({ data: { message: { id: 1 } } })
  }
  const component = renderComponent(<MyComponent />, { dataFetchInstance })
  ... your test
})

By creating a data function that accepts the requestData normally passed into axios that in turn returns a promise, you match the api of axios. The data variable can be set to the requestData and you can assert on the data being sent by any component using useDataFetch.

Note that in this instance it assumes that your components will only make a single call. To capture the output of multiple calls, do the following:

test('creates a new thing', async () => {
  const data = []
  const dataFetchInstance = (requestData) => {
    data.push(requestData)
    return Promise.resolve({ data: { message: { id: 1 } } })
  }
  const component = renderComponent(<MyComponent />, { dataFetchInstance })
  ... your test
  expect(data[0]).toEqual(..somedata)
  expect(data[1]).toEqual(..somedifferentData)
})

In a non-deterministic scenario (where it is unclear which order the calls will be made), it is recommended to filter for each call in the data array to perform your search (such as by the path of the data sent by axios):

const firstExpectedCall = data.find((d) => d.url == 'expected/1')
const secondExpectedCall = data.find((d) => d.url == 'expected/2')

Development

To run a development environment:

npm run start

You can then navigate to http://localhost:8080 and see the example app running. Using webpack serve, any changes you make to the src/ files will automatically be reflected.

Testing

Tests should be included in the __test__ file. To help in writing tests, a wrapper function has been provided in __tests__/test-utils.js. See __tests__/uncached-calls.js for examples on how to write tests.

Running Tests

To run tests:

npm run test

Contributing

  • Keep it respectful
  • Search issues for possible known issues (and help resolve them :))
  • Always add some form of test (see Testing) to any pull requests (or make a case for why the change is already covered)
  • Always update README.md with any new features/changes to behavior or usage.
  • Update CHANGELOG.md with each pull request (unless it makes sense not to)

Troubleshooting

  1. If you install into another project locally (using npm install <folder>) be sure to follow the advice found here https://stackoverflow.com/questions/56021112/react-hooks-in-react-library-giving-invalid-hook-call-error