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/reactor you can just use CDN: https://unpkg.com/@redux-requests/react.
$ npm install react-reduxUsage
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 ontypeorrequestKeychange, as well as onvariableschange, be aware that in order for this to work, you need to passvariablesandactionprops (see below)variables- required whenautoLoadistrue(or you want to useloadcallback) 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 ifautoLoadistrue(or you want to useloadcallback) andtypeisstring(so you don't use Redux action creator library and you have constants)autoReset: boolean:falseby default, setting totruewill 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 anotheruseQueryof the same typesuspense: boolean:falseby default, setting totruewill activate suspense, which will need to be catched by aSuspensecomponentthrowError: boolean:falseby default, setting totruewill throw error on query error, ready to be catched by a React error boundary componentsuspenseSsr: boolean:falseby default, set it totrueif you want to use suspense for SSR, typically you won't do it in everyuseQuerybut inRequestsProviderglobally
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 usemutatecallback and your mutation action accepts variables, you need to pass them as array, for examplevariables={['firstVariable', 2, 'thirdVariable']}action- necessary if you want to usemutatecallback andtypeisstring(so you don't use Redux action creator library and you have constants)autoReset: boolean:falseby default, setting totruewill reset mutation on component unmount, be adviced that nothing will be reset on unmount if there is any other active component with anotheruseMutationof the same typesuspense: boolean:falseby default, setting totruewill activate suspense, which will need to be catched by aSuspensecomponentthrowError: boolean:falseby default, setting totruewill 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 inuseQueryanduseMutation, required if you passstringtotype- that's it, you don't use action creator library, then you need to pass your subscription action hereactive: boolean:trueby default, setting tofalsewill 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 tohandleRequestfrom@redux-requests/core, not needed if you pass your ownstoreextraReducers: optional, object of your custom reducers which will be merged withrequestsReducer, not needed if you pass your ownstoregetMiddleware: optional, here you can pass extra Redux middleware, for examplegetMiddleware={requestsMiddleware => [thunkMiddleware, ...requestsMiddleware]}, not needed if you pass your ownstorestore: 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 alluseQueryanduseMutationhooksautoLoad: boolean: default tofalse, this value will be the default value for alluseQueryhooksautoReset: boolean: default tofalse, this value will be the default value for alluseQueryanduseMutationhooksthrowError: boolean: default tofalse, this value will be the default value for alluseQueryanduseMutationhookssuspenseSsr: boolean: default tofalse, set totruewhen using suspense SSRgetStore: function called onRequestsProvidermount, it allows you to get access tostorecreated 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 withrequestKeyautoReset: boolean: like foruseQueryanduseMutation, whether request should be reset on unmountchildren: React element withuseQueryoruseMutationhook, 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 togetQueryfrom the core libraryrequestKey: string: pass it if you usedrequestKeyin query action, refer togetQueryfrom the core librarymultiple: boolean: refer togetQueryfrom the core librarydefaultData: refer togetQueryfrom the core libraryselector: if you already have a query selector, pass it here instead oftypechildren- 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 toQueryisDataEmpty: query => boolean: function which defines whendatais empty, by default data as empty array and falsy value likenull,undefinedis considered as empty when data is updated - it will still show during initial fetch, but ill not for subsequent requestsnoDataMessage:stringor any React node, like<div>message</div>, which will be rendered whendatais emptyerrorComponent: custom React component, which will be rendered on error, receiveserrorprop,nullby defaulterrorComponentProps: extra props which will be passed toerrorComponentloadingComponentcustom React component, which will be rndered when request is pending, useful for showing spinners, receivesdownloadProgressanduploadProgressprops, useful whenrequestAction.meta.measureDownloadProgressorrequestAction.meta.measureUploadProgressistrueloadingComponentProps: extra props which will be passed toloadingComponentspinners,nullby defaultshowLoaderDuringRefetch: boolean:trueby default, whetherloadingComponentwill be shown on data refetch or not, iffalseyou 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 togetMutationfrom the core libraryrequestKey: string: pass it if you usedrequestKeyin mutation action, refer togetMutationfrom 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>;
redux-requests