redux-repository
A versatile set of pure functions to simplify the management of remote resources with Redux.
- A single resource consists of:
- ID
- status: requested, received, failed
- data, if the status is received
- error, if the status is failed
- timestamp of the data or error acquisition
- The same resource can be requested from multiple places at the same time, it will only be fetched once
- Resources are stored in the normalized state shape
- Resources can be cached to skip consequent fetching
- Read-only operations are supported so far: fetch and reset (remove local copy)
Quick Start
Install
npm install redux-repository
Use
Implement action creators first:
import { createFetchResource, createResetResources } from 'redux-repository/lib/actions';
import { Action } from 'redux-repository/lib/types';
import { ThunkAction } from 'redux-thunk';
import { Product } from './Product';
import { State } from './State';
export interface FetchProductAction {
(id: string): void;
}
export interface ResetProductsAction {
(): void;
}
export const fetchProduct = (id: string): ThunkAction<void, State, null, Action<Product, string>> => (
createFetchResource(
'product',
id,
({ catalog: { products } }) => products,
(dispatchReceived, dispatchFailed) => {
fetch(`https://example.com/api/products/${id}`)
.then(response => response.json())
.then(data => dispatchReceived(data))
.catch(error => dispatchFailed(error.toString()));
},
{
silentAlready: true, // skip "already received" messages, optional
ttl: 60 * 1000, // cache for 1 minute, optional
},
)
);
export const resetProducts = (): ThunkAction<void, State, null, Action<Product, string>> => (
createResetResources('product')
);
Then, inject the repository reducer:
import { Action } from 'redux';
import { isResourceAction, repositoryReducer } from 'redux-repository/lib/reducer';
import { createInitialState } from 'redux-repository/lib/repository';
import { Action as ReduxRepositoryAction } from 'redux-repository/lib/types';
import { Product } from './Product';
import { State } from './State';
const initialState: State = {
// ...
catalog: {
// ...
products: createInitialState(),
},
};
export default (state: State = initialState, action: Action): State => {
if (isResourceAction('product', action as ReduxRepositoryAction<Product, string>)) {
return {
...state,
catalog: {
...state.catalog,
products: repositoryReducer(state.catalog.products, action as ReduxRepositoryAction<Product, string>),
},
};
}
switch (action.type) {
// ...
default:
return state;
}
};
That's it! Now you can trigger fetchProduct
, resetProducts
and wire components to the repository via state:
import { connect } from 'react-redux';
import { Repository } from 'redux-repository/lib/interfaces';
import { fetchProduct, FetchProductAction } from './actions';
import { Product } from './Product';
import { State } from './State';
interface StateProps {
products: Repository<Product, string>;
}
interface DispatchProps {
fetchProduct: FetchProductAction;
}
const mapStateToProps = ({ catalog: { products } }: State): StateProps => ({ products });
const mapDispatchToProps: DispatchProps = { fetchProduct };
export const connect = connect(mapStateToProps, mapDispatchToProps);
The full list of exported entities that might be useful:
import {
createFetchResource,
createResetResources,
} from 'redux-repository/lib/actions';
import {
RequestedResource,
ReceivedResource,
FailedResource,
Resource,
Repository,
} from 'redux-repository/lib/interfaces';
import {
isResourceAction,
repositoryReducer,
} from 'redux-repository/lib/reducer';
import {
createInitialState,
getResourceById,
getResourcesArrayByIds,
pushResource,
pushResourcesArray,
mergeRepositories,
} from 'redux-repository/lib/repository';
import {
createFailed,
createReceived,
createRequested,
extractData,
extractError,
isExpired,
isFailed,
isReceived,
isRequested,
} from 'redux-repository/lib/resource';
import {
Action,
} from 'redux-repository/lib/types';
// Examples:
const productResource = getResourceById(productsRepository, productId);
const productData = extractData(productResource);
const productProgress = isRequested(productResource);