Redux Query React Integration
redux-query-react provides the React-specific interface for integrating with redux-query, a library designed for managing network state within Redux applications. It streamlines the process of fetching data, handling optimistic updates, and managing cancellations directly from React components. The current stable release candidate is `3.6.1-rc.2`, indicating active development with ongoing release candidates. While a precise release cadence isn't specified, the project exhibits a feature-driven release model. Key differentiators include its adherence to core Redux principles (middleware, actions, selectors, reducers), ensuring minimal 'magic' and high extensibility, allowing for custom middleware, UI integrations, and network interfaces. It offers both modern React hooks (like `useRequest`, `useMutation`) and higher-order components (`connectRequest`) to colocate data dependencies with components and trigger requests on mount or update, working seamlessly with both functional and class components.
Common errors
-
Error: Invariant Violation: Hooks can only be called inside of the body of a function component.
cause `useRequest` or `useMutation` is being called outside of a React functional component or a custom hook that adheres to React's rules of hooks.fixEnsure `useRequest` and `useMutation` are only invoked directly within the top level of a functional component or a custom hook that follows React's rules for calling hooks. -
Invariant Violation: Could not find `store` in the context of `<Connect(Component)>`
cause The `redux-query` middleware or reducer is not correctly integrated into your Redux store, or your React components are not wrapped within a `<Provider store={store}>` from `react-redux`.fixVerify that `queriesReducer` is added to your root reducer and `queriesMiddleware` is applied to your Redux store. Ensure the top-level component that uses `redux-query-react` hooks or HOCs is rendered inside a `react-redux` `<Provider>` component. -
Module not found: Error: Can't resolve 'redux-query-react' in '...'
cause The `redux-query-react` package is not installed in your project, or there is a typo in the import path.fixRun `npm install redux-query-react` or `yarn add redux-query-react` to install the package. Double-check your import statements for any typos in the package name or path.
Warnings
- breaking Major architectural changes in `redux-query` v2 required significant upgrades. The `request` fields in the `queries` reducer and actions were replaced with `networkHandler`, and rollback behavior for mutations was updated. Users migrating from `1.x` to `2.x` must consult the v2 transition guide for `redux-query`.
- gotcha `redux-query-react` has specific peer dependency requirements, notably for `react-redux@7.1.0` and `redux-query@^3.0.0-alpha.10`. Mismatches with these versions can lead to runtime errors or unexpected behavior due to API changes in these core libraries.
- gotcha While `redux-query-react` gained support for Redux v4 in `v2.3.1`, upgrading Redux itself in your application may introduce other breaking changes or require adjustments in your Redux store configuration outside of `redux-query`.
- breaking `redux-query` v2 introduced new, safer rollback behavior when mutations fail, along with a `rollback` option in query configs. Developers relying on optimistic updates in earlier versions might need to adjust their mutation logic to account for these changes.
Install
-
npm install redux-query-react -
yarn add redux-query-react -
pnpm add redux-query-react
Imports
- useRequest
const useRequest = require('redux-query-react').useRequest;import { useRequest } from 'redux-query-react'; - useMutation
import useMutation from 'redux-query-react';
import { useMutation } from 'redux-query-react'; - connectRequest
const connectRequest = require('redux-query-react').connectRequest;import { connectRequest } from 'redux-query-react';
Quickstart
import React from 'react';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { queriesReducer, queriesMiddleware, QueryConfig } from 'redux-query';
import { useRequest, useMutation } from 'redux-query-react';
import { createNetworkHandler } from 'redux-query-interface-superagent'; // For a real app, install this
// A mock network handler for demonstration purposes
const mockNetworkHandler = createNetworkHandler({
adapter: (config) => {
console.log('Mock API call:', config.url, config.method, config.body);
return new Promise((resolve, reject) => {
setTimeout(() => {
if (config.url === '/api/users/1') {
resolve({
status: 200,
body: { id: 1, name: 'Alice', email: 'alice@example.com' },
headers: new Headers(),
});
} else if (config.url === '/api/users/1' && config.method === 'PUT') {
resolve({
status: 200,
body: { id: 1, name: config.body.name, email: 'alice@example.com' },
headers: new Headers(),
});
} else {
reject({ status: 404, body: 'Not Found', headers: new Headers() });
}
}, 500);
});
}
});
// Setup Redux store with redux-query
const rootReducer = combineReducers({
queries: queriesReducer,
// Add other reducers here
});
const store = createStore(
rootReducer,
applyMiddleware(queriesMiddleware(mockNetworkHandler))
);
interface User {
id: number;
name: string;
email: string;
}
// Define a query config for fetching a user
const getUserQuery: QueryConfig = {
url: '/api/users/1',
update: {
entities: (prevEntities: any = {}, newEntities: any) => ({
...prevEntities,
user: newEntities.user, // Assuming API returns { user: { ... } }
}),
},
// Map the query response data to a specific key, if needed.
// data: (queryState) => queryState.entities.user
};
const UserProfile: React.FC = () => {
// Use useRequest to fetch user data
const { isPending, isFinished, data } = useRequest<User>(getUserQuery);
// Use useMutation to update user data with optimistic updates
const [updateUserName, { isPending: isUpdatingName }] = useMutation(
(newName: string) => ({
url: '/api/users/1',
method: 'PUT',
body: { name: newName },
update: {
entities: (prevEntities: any = {}, newEntities: any) => ({
...prevEntities,
user: newEntities.user,
}),
},
optimistic: {
entities: (prevEntities: any = {}) => ({
...prevEntities,
user: { ...prevEntities.user, name: newName + ' (optimistic)' },
}),
},
})
);
const handleRenameUser = () => {
updateUserName('Bob');
};
if (isPending) return <div>Loading user profile...</div>;
if (!data) return <div>No user data available.</div>;
return (
<div>
<h2>User Profile</h2>
<p>ID: {data.id}</p>
<p>Name: {data.name} {isUpdatingName && '(Updating...)'}</p>
<p>Email: {data.email}</p>
<button onClick={handleRenameUser} disabled={isUpdatingName}>Rename to Bob</button>
</div>
);
};
const App: React.FC = () => (
<Provider store={store}>
<UserProfile />
</Provider>
);
export default App;