@pinkairship/use-data-fetch
v3.3.5
Published
A data fetch hook that stays out of your way.
Downloads
945
Maintainers
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:
- Simplified onMount datafetch management
- Greater control over refetching
- 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):
- At the
get|post|put|patch|...
level - At the
useDataFetch
level - 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
anduseFetchOnMount
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:
- Provider level - this is the first level and defines the configuration set when making a
DataFetchProvider
- Hook Level - this is the second level and defines the configuration set when using the
useDataFetch
hook. Settings here override the Provider level. - 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
- 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