app-data
v1.0.0
Published
A file-system based state management library for React that uses convention over configuration to eliminate boilerplate.
Downloads
10
Readme
app-data (aka context-auto)
A file-system based state management library for React that uses convention over configuration to eliminate boilerplate.
Overview
context-auto is a unique state management solution that leverages your project's directory structure to automatically create and wire up state slices, actions, and reducers. Built on React's Context API, it provides a powerful and intuitive developer experience with zero boilerplate.
Features
- Zero Boilerplate: No action types, action creators, or manual store configuration
- File-System Based: Your folder structure defines your state shape and actions
- Immutable State: State exposed via Proxy to prevent accidental mutations
- First-Class Async Support: Built-in handling for async operations with automatic loading states
- Lazy Loading: Support for on-demand data fetching
- TypeScript Ready: Full TypeScript support (if applicable)
- Developer Friendly: Intuitive API that feels natural to use
Quick Start
1. Setup the Provider
Wrap your app with the StateProvider:
// src/App.js
import React from 'react';
import { StateProvider } from 'app-data';
import MyComponent from './MyComponent';
function App() {
return (
<StateProvider>
<MyComponent />
</StateProvider>
);
}
export default App;2. Create Your State Structure
Organize your state using folders. Each folder represents a state slice:
/src
/store
/user
INDEX.js # Initial state & reducer
login.js # Synchronous action
fetchProfile.js # Async action
/cart
INDEX.js # Initial state & reducer
addItem.js # Synchronous action
checkout.js # Async action3. Use in Components
import useStore from 'app-data';
function MyComponent() {
const store = useStore();
// Access state
const user = store.user;
// Call actions
const handleLogin = () => {
store.user.login({ username: 'john' });
};
// Check async loading states
const isLoading = store.user.loading.fetchProfile;
return (
<div>
<h1>Welcome {user.name}</h1>
{isLoading && <span>Loading...</span>}
<button onClick={handleLogin}>Login</button>
</div>
);
}File Conventions
INDEX.js - Initial State
Each state slice must have an INDEX.js file that exports a default function returning the initial state:
// store/user/INDEX.js
export default function() {
return {
name: '',
email: '',
isAuthenticated: false
};
}Action Files
Synchronous Actions
Export a single default function that receives the current state and payload:
// store/user/login.js
export default function(state, payload) {
return {
...state,
name: payload.name,
isAuthenticated: true
};
}Asynchronous Actions
Export four named functions to handle async lifecycle:
// store/user/fetchProfile.js
export function action(payload) {
// Return a promise or undefined to cancel
return fetch(`/api/user/${payload.id}`)
.then(res => res.json());
}
export function pending(state, payload) {
// State while loading
return { ...state, loading: true };
}
export function fulfilled(state, payload, response) {
// State on success
return {
...state,
...response,
loading: false
};
}
export function rejected(state, payload, error) {
// State on error
return {
...state,
error: error.message,
loading: false
};
}Special Files
INIT.js - Startup Actions
Runs automatically when the app loads:
// store/user/INIT.js
export default async function() {
const token = localStorage.getItem('token');
if (token) {
return await validateToken(token);
}
}DEFER.js - Lazy Loading
Fetches data the first time the state slice is accessed:
// store/products/DEFER.js
export default async function() {
return await fetch('/api/products')
.then(res => res.json());
}Loading States
Async actions automatically track their loading state:
const store = useStore();
// Before action is called
store.user.loading.save // undefined
// During action
store.user.loading.save // true
// After success
store.user.loading.save // false
// After error
store.user.loading.save // Error object
// Clear loading state
store.user.save.clear();
// or
store.user.loading.save.clear();Advanced Usage
Accessing Multiple State Slices
const store = useStore();
const { user, cart, products } = store;Conditional Actions
const store = useStore();
if (!store.user.isAuthenticated) {
store.user.login(credentials);
}Error Handling
const store = useStore();
const error = store.user.loading.save;
if (error instanceof Error) {
console.error('Save failed:', error.message);
error.clear(); // Reset error state
}API Reference
<StateProvider>
Wraps your app to provide state management.
useStore()
Hook that returns the store object with all state slices and actions.
State Slice Properties
- Direct access to state values
.loading.[actionName]- Loading state for async actions- Action methods automatically attached
Action Methods
store.[slice].[action](payload)- Execute actionstore.[slice].[action].clear()- Clear async action state
Best Practices
- Keep state slices focused and single-purpose
- Use INIT.js for authentication checks
- Use DEFER.js for expensive initial data loads
- Name actions descriptively (verb + noun)
- Handle errors gracefully in rejected functions
License
MIT
