@feardread/feature-factory
v5.1.2
Published
Library to interact with redux toolkit and reduce boilerplate / repeated code
Maintainers
Readme
Redux Factory Suite
A comprehensive collection of factory utilities for Redux applications, providing standardized patterns for creating slices, async thunks, state management, and caching. This suite eliminates boilerplate code and enforces consistent patterns across your Redux application.
Table of Contents
Overview
The Redux Factory Suite consists of five main components:
- FeatureFactory - Creates complete Redux features with slices, async actions, and reducers
- ThunkFactory - Generates standardized async thunks for API operations
- StateFactory - Creates comprehensive initial state structures
- CacheFactory - Provides browser storage utilities with fallback support
- ApiFactory - Creates RTK Query APIs with standardized CRUD operations and caching
Installation
npm install @feardread/feature-factory
# The factory utilities are included in your project sourcedependencies:
npm install @reduxjs/toolkit react-reduxCore Components
FeatureFactory
Creates complete Redux features with standardized structure including slices, async actions, and reducers.
import { FeatureFactory } from './FeatureFactory';
const userFeature = FeatureFactory('users', {
setCurrentUser: (state, action) => {
state.currentUser = action.payload;
}
});
const { slice, asyncActions } = userFeature.create();ThunkFactory
Factory for creating Redux async thunks with standardized patterns for HTTP operations.
import ThunkFactory from './ThunkFactory';
// Create individual thunks
const fetchUsers = ThunkFactory.create('users', 'all');
const createUser = ThunkFactory.post('users', 'create');
// Create full CRUD suite
const userThunks = ThunkFactory.createCrud('users');StateFactory
Creates comprehensive initial state structures with optional features like pagination, filtering, and validation.
import StateFactory from './StateFactory';
// Full-featured state
const initialState = StateFactory('users');
// Minimal state
const minimalState = StateFactory.minimal('users');
// List-optimized state
const listState = StateFactory.forList('users');CacheFactory
Browser storage utilities with automatic fallback to memory storage.
import CacheFactory from './CacheFactory';
const cache = CacheFactory({ type: 'local', prefix: 'myapp_' });
await cache.set('user', userData);
const user = await cache.get('user');ApiFactory
Creates RTK Query APIs with standardized CRUD operations, caching, and tag invalidation.
import ApiFactory from './ApiFactory';
// Create complete API with standard CRUD endpoints
const usersApi = ApiFactory.createComplete('users');
// Create custom API
const customApi = ApiFactory('products')
.inject('getFeatured', {
method: 'GET',
url: 'products/featured',
providesTags: ['Product']
})
.create();Quick Start
Here's how to create a complete feature using all components:
import { FeatureFactory } from './FeatureFactory';
import StateFactory from './StateFactory';
import ThunkFactory from './ThunkFactory';
import ApiFactory from './ApiFactory';
// 1. Create RTK Query API (recommended for new projects)
const productsApi = ApiFactory.createComplete('products', {
tagTypes: ['Product', 'Category']
});
// OR use traditional async thunks
// 1. Create initial state
const initialState = StateFactory.forList('products');
// 2. Create custom async actions
const customActions = {
searchProducts: ThunkFactory.create('products', 'search'),
getPopularProducts: ThunkFactory.custom('products', 'popular', {
method: 'GET',
customUrl: 'products/popular'
})
};
// 3. Create the feature
const productFeature = FeatureFactory('products', {
toggleFavorite: (state, action) => {
const product = state.products.find(p => p.id === action.payload);
if (product) {
product.isFavorite = !product.isFavorite;
}
}
});
// 4. Generate slice and actions
const { slice, asyncActions } = productFeature.create({
service: customActions,
initialState
});
export const productReducer = slice.reducer;
export const productActions = { ...slice.actions, ...asyncActions };
// For RTK Query approach, use generated hooks:
// const { data, error, isLoading } = productsApi.useGetAllQuery();API Reference
FeatureFactory API
FeatureFactory(entity, reducers, endpoints)
Creates a feature factory instance.
Parameters:
entity(string): Entity name for the featurereducers(Object): Custom reducers to include in the sliceendpoints(Object|null): API endpoints (currently unused)
Returns: Feature factory instance
Methods
create(options): Creates slice and async actionsmanager(initialReducers): Creates dynamic reducer managerinject(source, destination): Merges objectsgetEntityName(): Returns entity namegetAdapter(): Returns RTK entity adapter
ThunkFactory API
Static Methods
create(entity, prefix): Creates GET request thunkpost(entity, prefix): Creates POST request thunkput(entity, prefix): Creates PUT request thunkpatch(entity, prefix): Creates PATCH request thunkdelete(entity, prefix): Creates DELETE request thunkcustom(entity, prefix, options): Creates custom configured thunkcreateCrud(entity): Creates complete CRUD thunk suite
Standard Operations
all: GET /entity/allone: GET /entity/:idsearch: GET /entity/search?paramscreate: POST /entity/createupdate: PUT /entity/:idpatch: PATCH /entity/:iddelete: DELETE /entity/:id
StateFactory API
StateFactory(namespace, options)
Creates comprehensive initial state.
Parameters:
namespace(string): Entity namespaceoptions(Object): Configuration options
Options:
includeEntityState(boolean): Include entity-specific stateincludePagination(boolean): Include pagination stateincludeFiltering(boolean): Include filtering stateincludeSorting(boolean): Include sorting stateincludeSelection(boolean): Include selection stateincludeValidation(boolean): Include validation stateincludeMetadata(boolean): Include metadata statecustomFields(Object): Custom fields to add
Preset Methods
StateFactory.minimal(namespace): Minimal state structureStateFactory.forList(namespace): List/table optimized stateStateFactory.forForm(namespace): Form optimized stateStateFactory.forRealTime(namespace): Real-time data stateStateFactory.normalized(namespace): Normalized entities stateStateFactory.withOperations(namespace, operations): Custom operations tracking
CacheFactory API
CacheFactory(options)
Creates cache instance.
Options:
type(string): 'local' or 'session'prefix(string): Key prefix for namespacingenableLogging(boolean): Enable debug loggingfallbackToMemory(boolean): Use memory storage fallbackmaxRetries(number): Maximum retry attempts
Methods
set(key, value): Store valueget(key, defaultValue): Retrieve valueremove(key): Remove valueclear(): Clear all valueskeys(): Get all keyshas(key): Check if key existsgetStats(): Get storage statisticsbulk.set(items): Set multiple itemsbulk.get(keys): Get multiple itemsbulk.remove(keys): Remove multiple items
ApiFactory API
ApiFactory(sliceName, options)
Creates an RTK Query API factory instance.
Parameters:
sliceName(string): Entity/slice nameoptions(Object): Configuration options
Options:
tagTypes(string[]): Cache tag types for invalidationbaseQuery(Object): Custom base query configurationincludeStandardEndpoints(boolean): Include CRUD endpoints
Static Methods
createComplete(sliceName, options): Creates API with all standard endpointscreateCustom(sliceName, endpoints, options): Creates API with only custom endpointsgetStandardEndpoints(): Returns standard endpoint configurationsgetHttpMethods(): Returns HTTP methods constants
Instance Methods
inject(name, config): Inject single endpointinjectMany(endpoints): Inject multiple endpointscreate(): Create the final APIcreateStandard(): Add standard CRUD endpointscreateBulk(): Add bulk operation endpointscreateCustom(name, config): Add custom endpointwithAuth(token): Create API with authenticationwithBaseUrl(url): Create API with custom base URL
Standard Endpoints Generated
getAll: GET /entity/allgetById: GET /entity/:idcreate: POST /entityupdate: PUT /entity/:idpatch: PATCH /entity/:iddelete: DELETE /entity/:idsearch: GET /entity/search
Examples
Complete User Management Feature
// userFeature.js
import { FeatureFactory } from './FeatureFactory';
import StateFactory from './StateFactory';
import ThunkFactory from './ThunkFactory';
// Create state optimized for user lists
const initialState = StateFactory.forList('users');
// Create custom service actions
const userService = {
fetchProfile: ThunkFactory.custom('users', 'profile', {
method: 'GET',
customUrl: 'auth/profile'
}),
updateProfile: ThunkFactory.put('users', 'profile'),
changePassword: ThunkFactory.post('users', 'change-password')
};
// Create feature factory
const userFeature = FeatureFactory('users', {
setActiveUser: (state, action) => {
state.activeUser = action.payload;
},
clearUsers: (state) => {
state.data = [];
state.usersList = [];
}
});
// Generate the complete feature
const { slice, asyncActions } = userFeature.create({
service: userService,
initialState
});
export const userReducer = slice.reducer;
export const userActions = { ...slice.actions, ...asyncActions };Using the Cache System
// cacheService.js
import CacheFactory from './CacheFactory';
// Create application cache
const appCache = CacheFactory({
type: 'local',
prefix: 'myapp_',
enableLogging: process.env.NODE_ENV === 'development'
});
// Cache service
export const cacheService = {
async cacheUserData(userId, userData) {
await appCache.set(`user_${userId}`, userData);
},
async getUserData(userId) {
return await appCache.get(`user_${userId}`);
},
async clearUserCache(userId) {
await appCache.remove(`user_${userId}`);
},
async cacheUserPreferences(preferences) {
await appCache.set('user_preferences', preferences);
}
};RTK Query API with ApiFactory
// productsApi.js
import ApiFactory from './ApiFactory';
// Create complete API with all CRUD operations
export const productsApi = ApiFactory.createComplete('products', {
tagTypes: ['Product', 'Category', 'Review'],
baseQuery: {
baseUrl: process.env.REACT_APP_API_URL,
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
}
}
});
// Add custom endpoints
const enhancedApi = productsApi.injectEndpoints({
endpoints: (builder) => ({
getFeaturedProducts: builder.query({
query: () => 'products/featured',
providesTags: ['Product'],
}),
getProductsByCategory: builder.query({
query: (categoryId) => `products/category/${categoryId}`,
providesTags: (result, error, categoryId) => [
{ type: 'Product', id: `CATEGORY_${categoryId}` },
],
}),
toggleProductFavorite: builder.mutation({
query: ({ id, isFavorite }) => ({
url: `products/${id}/favorite`,
method: 'PATCH',
body: { isFavorite },
}),
invalidatesTags: (result, error, { id }) => [
{ type: 'Product', id },
],
}),
}),
});
// Export hooks for use in components
export const {
useGetAllQuery,
useGetByIdQuery,
useCreateMutation,
useUpdateMutation,
useDeleteMutation,
useGetFeaturedProductsQuery,
useGetProductsByCategoryQuery,
useToggleProductFavoriteMutation,
} = enhancedApi;Using ApiFactory in Components
// ProductList.jsx
import React from 'react';
import {
useGetAllQuery,
useDeleteMutation,
useGetFeaturedProductsQuery
} from '../api/productsApi';
const ProductList = () => {
const {
data: products = [],
error,
isLoading
} = useGetAllQuery();
const {
data: featuredProducts = []
} = useGetFeaturedProductsQuery();
const [deleteProduct, { isLoading: isDeleting }] = useDeleteMutation();
const handleDelete = async (productId) => {
try {
await deleteProduct({ id: productId }).unwrap();
// Success - cache automatically invalidated
} catch (error) {
console.error('Failed to delete product:', error);
}
};
if (isLoading) return <div>Loading products...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<section>
<h2>Featured Products</h2>
{featuredProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</section>
<section>
<h2>All Products</h2>
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onDelete={() => handleDelete(product.id)}
isDeleting={isDeleting}
/>
))}
</section>
</div>
);
};Advanced State Management
// advancedState.js
import StateFactory from './StateFactory';
// Create state for a complex dashboard
const dashboardState = StateFactory('dashboard', {
includeMetadata: true,
includePagination: true,
customFields: {
widgets: [],
layout: 'grid',
theme: 'light',
notifications: [],
isFullscreen: false,
lastRefresh: null
}
});
// Create form state with validation
const userFormState = StateFactory.forForm('userForm');
// Create real-time chat state
const chatState = StateFactory.forRealTime('chat');Best Practices
1. Entity Naming
Use consistent, descriptive entity names:
// Good
const userFeature = FeatureFactory('users');
const productFeature = FeatureFactory('products');
// Avoid
const feature1 = FeatureFactory('u');
const myFeature = FeatureFactory('thing');2. State Organization
Choose appropriate state factories for your use case:
// For data tables/lists
const listState = StateFactory.forList('products');
// For forms
const formState = StateFactory.forForm('userForm');
// For simple data
const minimalState = StateFactory.minimal('settings');3. Async Actions
Use descriptive prefixes for custom actions:
const customActions = {
fetchUserProfile: ThunkFactory.create('users', 'profile'),
searchActiveUsers: ThunkFactory.create('users', 'active-search'),
bulkUpdateUsers: ThunkFactory.post('users', 'bulk-update')
};4. Error Handling
Always handle async action states in components:
const { data, loading, error } = useSelector(state => state.users);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;5. Caching Strategy
Use appropriate cache types and prefixes:
// Session data (cleared on browser close)
const sessionCache = CacheFactory({ type: 'session' });
// Persistent data
const persistentCache = CacheFactory({
type: 'local',
prefix: 'app_v1_'
});6. RTK Query vs Async Thunks
Choose the appropriate data fetching approach:
// RTK Query - Recommended for new projects (automatic caching, background refetching)
const usersApi = ApiFactory.createComplete('users');
// Async Thunks - Better for complex business logic or existing codebases
const userFeature = FeatureFactory('users').create({
service: ThunkFactory.createCrud('users')
});7. API Configuration
Configure APIs with proper error handling and authentication:
const api = ApiFactory('products', {
baseQuery: {
baseUrl: process.env.REACT_APP_API_URL,
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('authorization', `Bearer ${token}`);
}
return headers;
},
},
tagTypes: ['Product', 'Category']
});TypeScript Support
The factories can be used with TypeScript by defining interfaces:
interface User {
id: number;
name: string;
email: string;
}
interface UserState {
users: User[];
currentUser: User | null;
loading: boolean;
error: string | null;
}
// Use with proper typing
const userFeature = FeatureFactory<UserState>('users', reducers);Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Dependencies
@reduxjs/toolkit- Required for Redux functionality- Modern browser with ES6+ support
- Node.js 14+ for development
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
For older browsers, the CacheFactory will automatically fallback to memory storage if localStorage/sessionStorage is unavailable.
