Usage with React
Introduction
This library is just a Redux addon, so you can use it with any UI library, like React, Angular, Vue - whatever will work with Redux will work. However, you might consider using some React helpers which will be described below.
Installation
To install the package, just run:
$ npm install @redux-requests/react
or you can just use CDN: https://unpkg.com/@redux-requests/react
.
$ npm install react-redux
Usage
useQuery
useQuery
is a hook which uses useSelector
from react-redux
together with getQuerySelector
from
redux-requests/core
. It accepts the same arguments as getQuerySelector
. You could
easily use useSelector
directly, but useQuery
is slightly less verbose. So, without useQuery
:
import React from 'react';import { getQuerySelector } from '@redux-requests/core';import { useSelector } from 'react-redux';
const Books = () => { const books = useSelector(getQuerySelector({ type: 'FETCH_BOOKS' })); // ...};
and with useQuery
:
import React from 'react';import { useQuery } from '@redux-requests/react';
const Books = () => { const { data, error, loading, pristine } = useQuery({ type: 'FETCH_BOOKS' }); // ...};
As you can see, useQuery
hook is a convenient way to read query state, however it is much more powerful than that, it supports:
- automatic queries reset and abort on component unmount
- automatic query load - that's it, it can dispatch query action automatically for you on component mount
- suspense support
- server side suspense support for SSR - see this guide for more information
- throwing errors instead of rendering to catch them up by a React error boundary component
Above can be configured with extra props:
requestKey: string
- pass it if you used requestKey in the associated queryautoLoad: boolean
- defaultfalse
, iftrue
, then query action will be dispatched on component mount automatically, also, query will be dispatched again ontype
orrequestKey
change, as well as onvariables
change, be aware that in order for this to work, you need to passvariables
andaction
props (see below)variables
- required whenautoLoad
istrue
(or you want to useload
callback) and your query action accepts variables, you need to pass them as array, for examplevariables={['firstVariable', 2, 'thirdVariable']}
, note that query will be refetched on any variable change, so you must pay attention if any of your variables is an object - if yes, it must be memoized, for example byuseMemo
, otherwise query would be refetched on every renderaction
- necessary ifautoLoad
istrue
(or you want to useload
callback) andtype
isstring
(so you don't use Redux action creator library and you have constants)autoReset: boolean
:false
by default, setting totrue
will reset query on component unmount, be adviced though that it won't reset queries which are cached, also nothing will be reset on unmount if there is any other active component with anotheruseQuery
of the same typesuspense: boolean
:false
by default, setting totrue
will activate suspense, which will need to be catched by aSuspense
componentthrowError: boolean
:false
by default, setting totrue
will throw error on query error, ready to be catched by a React error boundary componentsuspenseSsr: boolean
:false
by default, set it totrue
if you want to use suspense for SSR, typically you won't do it in everyuseQuery
but inRequestsProvider
globally
To get some more ideas how to use it, see below example:
const fetchBook = id => ({ type: 'FETCH_BOOK', request: { url: `/books/${id}` },});
const Book = () => { const { data, error, loading, pristine, load, // callback to dispatch this query action any time stopPolling, // callback to stop polling for this query action, if set } = useQuery({ type: 'FETCH_BOOK', action: fetchBook, variables: [1], autoLoad: true, autoReset: true, suspense: true, throwError: true, }); // ...};
Note, that unconvenient action
won't be necessary if you use an action creator library.
useMutation
useMutation
is a hook which uses useSelector
from react-redux
together with getMutationSelector
from
redux-requests
. It accepts the same arguments as getMutationSelector
. Like in case of useQuery
, you could
render
For example:
import React from 'react';import { useMutation } from '@redux-requests/react';
const Books = () => { const { loading, error } = useMutation({ type: 'DELETE_BOOK' }); // ...};
Like in case of useQuery
, useMutation
also accepts some extra props:
requestKey: string
- pass it if you used requestKey in the associated mutationvariables
- required when you want to usemutate
callback and your mutation action accepts variables, you need to pass them as array, for examplevariables={['firstVariable', 2, 'thirdVariable']}
action
- necessary if you want to usemutate
callback andtype
isstring
(so you don't use Redux action creator library and you have constants)autoReset: boolean
:false
by default, setting totrue
will reset mutation on component unmount, be adviced that nothing will be reset on unmount if there is any other active component with anotheruseMutation
of the same typesuspense: boolean
:false
by default, setting totrue
will activate suspense, which will need to be catched by aSuspense
componentthrowError: boolean
:false
by default, setting totrue
will throw error on mutation error, ready to be catched by a React error boundary component
To get some more ideas how to use it, see below example:
const deleteBook = id => ({ type: 'DELETE_BOOK', request: { url: `/books/${id}`, method: 'delete' },});
const Book = () => { const { error, loading, pristine, mutate, // callback to dispatch this mutation action any time } = useQuery({ type: 'DELETE_BOOK', action: deleteBook, variables: [1], autoReset: true, suspense: true, throwError: true, }); // ...};
useSubscription
useSubscription
is a hook which automatically dispatches subscription actions. Also, on onmount, it automatically
stop subscription. For example:
import React from 'react';import { useSubscription } from '@redux-requests/react';
const Books = () => { useSubscription({ type: 'ON_BOOK_DELETION' }); // ...};
useSubscription
also accepts some extra props:
requestKey: string
: pass it if you used requestKey in the associated subscriptionvariables
- required if your subscription action accepts some argumentsaction
- like inuseQuery
anduseMutation
, required if you passstring
totype
- that's it, you don't use action creator library, then you need to pass your subscription action hereactive: boolean
:true
by default, setting tofalse
will prevent subscription action to be dispatched
RequestsProvider
This component is only necessary for SSR with suspense, but it is recommended to use it in any case, as it can change default values like autoReset
for useQuery
and useMutation
hooks and optionally setup Redux store for you. It is actually a wrapper around
Provider
from react-redux
, so you shouldn't use both, if you use RequestsProvider
, don't use Provider
.
RequestsProvider
accepts the following props:
children
: required, any React element, probably top level of you apprequestsConfig
: the very same config you would pass tohandleRequest
from@redux-requests/core
, not needed if you pass your ownstore
extraReducers
: optional, object of your custom reducers which will be merged withrequestsReducer
, not needed if you pass your ownstore
getMiddleware
: optional, here you can pass extra Redux middleware, for examplegetMiddleware={requestsMiddleware => [thunkMiddleware, ...requestsMiddleware]}
, not needed if you pass your ownstore
store
: your store instance, pass it if you would like to create Redux store on your ownsuspense: boolean
: default tofalse
, this value will be the default value for alluseQuery
anduseMutation
hooksautoLoad: boolean
: default tofalse
, this value will be the default value for alluseQuery
hooksautoReset: boolean
: default tofalse
, this value will be the default value for alluseQuery
anduseMutation
hooksthrowError: boolean
: default tofalse
, this value will be the default value for alluseQuery
anduseMutation
hookssuspenseSsr: boolean
: default tofalse
, set totrue
when using suspense SSRgetStore
: function called onRequestsProvider
mount, it allows you to get access tostore
created byRequestsProvider
, typically used in suspense SSR scenarioinitialState
: initial state for store created byRequestsProvider
, not needed when passing your ownstore
, typically only for SSR scenario
Simple example:
import axios from 'axios';import { RequestsProvider } from '@redux-requests/react';import { createDriver } from '@redux-requests/axios';
<RequestsProvider requestsConfig={{ driver: createDriver( axios.create({ baseURL: 'http://localhost:3000', }), ), }} autoReset> <App /></RequestsProvider>;
RequestsErrorBoundary
This is a ready to use error boundary component to catch errors from useQuery
and useMutation
hooks, when using throwErrow: true
.
It has a convenient ability to recover from error if catched error is fixed, for example by a query refetch.
It has the following props:
type
: required, a type of a query or a mutation which you want to catch error forrequestKey
: like above, required when you use a request withrequestKey
autoReset: boolean
: like foruseQuery
anduseMutation
, whether request should be reset on unmountchildren
: React element withuseQuery
oruseMutation
hook, for which you want to catch errorsfallback
: render prop with error to render
Simple example:
import { RequestsErrorBoundary } from '@redux-requests/react';
<RequestsErrorBoundary type="FETCH-BOOK" autoReset fallback={error => <div>Some error happened during books loading</div>}> <Book /></RequestsErrorBoundary>;
useDispatchRequest
Alias for useDispatch
from react-redux
, it works the same but it improves Typescript experience:
import { useDispatchRequest } from '@redux-requests/react';
const BookFetcher = () => { const dispatchRequest = useDispatchRequest();
return ( <button onClick={async () => { const { data, error } = await dispatch(fetchBook('1')); }} > Fetch book </button> );};
See this guide for more information.
Query
Query
is render prop alternative to useQuery
hook. However, for now this component is not in par with useQuery
.
It can be only used to read query state, not to automatically load queries and so on. For that, use either useQuery
or create your own render prop component basing on useQuery
. Time will tell whether Query
will be deprecated or
will match all useQuery
functionality.
Anyway, Query
has the following props:
type: string
: type of query action, refer togetQuery
from the core libraryrequestKey: string
: pass it if you usedrequestKey
in query action, refer togetQuery
from the core librarymultiple: boolean
: refer togetQuery
from the core librarydefaultData
: refer togetQuery
from the core libraryselector
: if you already have a query selector, pass it here instead oftype
children
- render function receiving object with data, loading flag and error propertycomponent
- alternative prop to children, you can pass your custom component here, which will receive data, loading and error props, plus any additional props passed toQuery
isDataEmpty: query => boolean
: function which defines whendata
is empty, by default data as empty array and falsy value likenull
,undefined
is considered as empty when data is updated - it will still show during initial fetch, but ill not for subsequent requestsnoDataMessage
:string
or any React node, like<div>message</div>
, which will be rendered whendata
is emptyerrorComponent
: custom React component, which will be rendered on error, receiveserror
prop,null
by defaulterrorComponentProps
: extra props which will be passed toerrorComponent
loadingComponent
custom React component, which will be rndered when request is pending, useful for showing spinners, receivesdownloadProgress
anduploadProgress
props, useful whenrequestAction.meta.measureDownloadProgress
orrequestAction.meta.measureUploadProgress
istrue
loadingComponentProps
: extra props which will be passed toloadingComponent
spinners,null
by defaultshowLoaderDuringRefetch: boolean
:true
by default, whetherloadingComponent
will be shown on data refetch or not, iffalse
you won't see spinner even when data is being loaded if you already have some data from a previous requestsaction
: useful only when you use Typescript, see details here
Minimalistic example:
import { Query } from '@redux-requests/react';
<Query type={REQUEST_TYPE} // or selector={myQuerySelector}> {({ data }) => <div>{data}</div>}</Query>;
or with component
prop:
import { Query } from '@redux-requests/react';
const DataComponent = ({ query, extraLabelProp }) => ( <div> <h1>{extraLabelProp}</h1> {query.data} </div>);
<Query type={REQUEST_TYPE} // or selector={myQuerySelector} component={DataComponent} extraLabelProp="label"/>;
or with all props:
import { Query } from '@redux-requests/react';
const LoadingComponent = ({ label }) => ( <div> ...loading {label} </div>);
const ErrorComponent = ({ error, label }) => ( <div> Error with status code {error.status} {label} </div>);
<Query type={REQUEST_TYPE} // or selector={myQuerySelector} isDataEmpty={query => Array.isArray(query.data) ? query.data.length === 0 : !query.data } showLoaderDuringRefetch={false} noDataMessage="There is no data" errorComponent={ErrorComponent} errorComponentProps={{ label: 'Error label' }} loadingComponent={LoadingComponent} loadingComponentProps={{ label: 'Loading label' }}> {({ data }) => <div>{data}</div>}</Query>;
Mutation
Mutation
is render prop alternative to useMutation
hook. However, for now this component is not in par with useMutation
.
It can be only used to read mutation state, not to automatically reset mutations and so on. For that, use either useMutation
or create your own render prop component basing on useMutation
. Time will tell whether Mutation
will be deprecated or
will match all useMutation
functionality.
type: string
: type of mutation action, refer togetMutation
from the core libraryrequestKey: string
: pass it if you usedrequestKey
in mutation action, refer togetMutation
from the core libraryselector
: if you already have a mutation selector, pass it here instead of typechildren
- render function receiving object with loading flag and error propertycomponent
- alternative prop to children, you can pass your custom component here, which will receive loading and error props, plus any additional props passed to Mutation
You use it like this:
import { Mutation } from '@redux-requests/react';
<Mutation type={MUTATION_TYPE} // or selector={myMutationSelector}> {({ loading, error }) => { if (error) { return <div>Something went wrong</div>; }
return ( <button onClick={dispatchSomeMutation} disabled={loading}> Send mutation {loading && '...'} </button> ); }}</Mutation>;