noducs is a lightweight, boilerplate-free state management library for React applications. It simplifies global state management, offers seamless reactivity, supports middleware, and avoids unnecessary complexities like providers or extra wrapping components.
- What Does It Do?
- Why No
<Provider>
? - Features
- Installation
- Quick Start
- Middleware Support
- State Lifecycle
- Comparison with Other Libraries
- Best Practices
- FAQ
- Contributing
- License
Managing state in React often feels more complicated than it needs to be. Libraries like Redux and Context API require a lot of setup—reducers, actions, dispatchers, and components—just to get started. For many projects, this can feel like too much overhead.
What if managing state was as simple as using a JavaScript object, but with the power of React's reactivity built in?
That’s the idea behind noducs. We wanted to make a state management library that’s simple, flexible, and fits into your workflow without getting in the way.
With noducs, there’s no need for reducers or dispatch functions. You just create a slice of state and start using it.
import { createState } from "noducs";
// Create a simple counter state
const counterState = createState("counter", { count: 0 });
// Use the state in your component
export const useCounter = counterState.use;
// Actions to modify state
export const increment = () => counterState.set((state) => (state.count += 1));
export const decrement = () => counterState.set((state) => (state.count -= 1));
Use a for React-based global state when needed, or skip it entirely for simpler use cases.
import { StoreProvider } from "noducs";
const App = () => (
<StoreProvider>
<div>
<h1>My App</h1>
</div>
</StoreProvider>
);
import { storeManager } from "noducs";
// Access global state directly
console.log(storeManager.getState());
noducs automatically generates actions for every key in your state, so you don’t need to write them manually. Middleware lets you log, validate, or handle async actions with ease.
const userSlice = createSliceState({
name: "user",
initialState: { data: null, loading: false, error: null },
});
// Auto-generated actions
userSlice.actions.setData({ id: 1, name: "John Doe" });
userSlice.actions.setLoading(true);
// Add middleware
const loggerMiddleware = (state, updater, next) => {
console.log("Before:", state);
next(updater);
console.log("After:", state);
};
Easily access and update global state anywhere in your app, even outside of React components.
import { storeManager } from "noducs";
// Access global state
const globalState = storeManager.getState();
console.log("Global State:", globalState);
// Update global state manually
globalState["user"].data = { id: 2, name: "Jane Doe" };
storeManager.updateGlobalState();
Organize your app with modular state slices, and use middleware to handle more advanced use cases as your app grows.
const counterSlice = createSliceState({
name: "counter",
initialState: { count: 0 },
middlewares: [
(state, updater, next) => {
console.log("Middleware - Before:", state);
next(updater);
console.log("Middleware - After:", state);
},
],
});
counterSlice.actions.setCount(5); // Middleware logs will be triggered
noducs takes the pain out of state management. Whether you’re working on a small feature or a large app with complex state, it’s designed to make managing your state simple, intuitive, and scalable.
-
Global State Management: Manage global state effortlessly across your React components, with or without a provider.
-
Direct State Access: No need for dispatch or useSelector—access state directly as JavaScript variables.
-
Middleware Support: Add middleware to log, validate, or handle asynchronous actions during state updates.
-
Optional : Use a component when you need React-based global state management, or skip it entirely for direct access.
-
React Integration: Components automatically re-render when the state they depend on changes, ensuring seamless updates.
Unlike traditional libraries like Redux or Context API that enforce using a to wrap the entire application, noducs gives you the flexibility to choose how you manage your global state:
Default Behavior: No Provider Required
- Independent State Management: Each slice is independently managed, and you can access or update it directly.
- Global State Access: Use storeManager to access the entire global state without a .
- Manual Updates (Optional): Directly update slices and notify subscribers when necessary.
import { storeManager } from "noducs";
// Access global state directly
console.log(storeManager.getState());
// Update global state manually
storeManager.getState()["user"].data = { id: 1, name: "John Doe" };
storeManager.updateGlobalState();
When you want React-based global state management, you can wrap any part of your app with a component. The provider makes the global store accessible to all its children using React Context.
Scoped Provider: You can wrap any part of your application—not just the root. Automatic Updates: State changes propagate to all components using the useGlobalStore hook.
import React from "react";
import { StoreProvider, useGlobalStore } from "noducs";
const GlobalViewer = () => {
const globalState = useGlobalStore();
return <pre>{JSON.stringify(globalState, null, 2)}</pre>;
};
const App = () => (
<StoreProvider>
<div>
<h1>My App</h1>
<GlobalViewer />
</div>
</StoreProvider>
);
export default App;
You can combine the two approaches:
Use storeManager for utility functions or areas without React Context. Use for scoped or React-friendly state access.
Large apps may need React-based state management in some areas while relying on direct access in others. Developers can start simple and scale as needed.
If you choose manual updates, you can control when global state updates propagate to subscribers. This is useful for optimizing performance in large applications.
// Perform multiple updates
storeManager.getState()["counter"].count += 1;
storeManager.getState()["user"].data = { id: 2, name: "Jane Doe" };
// Notify subscribers once
storeManager.updateGlobalState();
✅ Minimal Boilerplate: No reducers, no actions, no dispatch—just pure state management.
✅ Auto-Generated Actions: Actions for updating state are automatically created based on the initial state.
✅ Direct State Access: Access and update state as easily as working with JavaScript variables.
✅ Reactivity Components re-render automatically when subscribed state changes.
✅ Middleware Support: Intercept, log, validate, or handle asynchronous actions during state updates
✅ Optional : Use React-based global state when needed, with flexible provider placement.
✅ Easy Setup: Start managing state in minutes, with intuitive APIs and minimal configuration.
✅ Redux DevTools Integration: Track and debug state changes effortlessly.
You might notice that some names in the library, like createSliceState or references to "actions," feel a little like Redux. That’s because I’m still transitioning from my old Redux habits to fully embracing the simplicity of noducs. 😊
Think of it as a small homage to Redux while I find my rhythm with this library. Rest assured, everything is designed to be way simpler and more intuitive—no reducers, dispatchers, or boilerplate required!
Using npm:
npm install noducs
Using yarn:
yarn add noducs
Getting started with noducs is easy. Here’s how you can create and use state slices, integrate middleware, and understand the library’s state lifecycle.
A state slice is a self-contained unit of state with actions and utilities for accessing or updating it.
import { createSliceState } from "noducs";
const counterSlice = createSliceState({
name: "counter",
initialState: { count: 0 },
});
export const useCounter = counterSlice.use;
export const { setCount } = counterSlice.actions;
export const increment = () => counterSlice.set((state) => (state.count += 1));
Access and update the state in your React components using the hooks and actions provided by your slice.
import React from "react";
import { useCounter, increment } from "./counterState";
const Counter: React.FC = () => {
const [counter] = useCounter();
return (
<div>
<h1>Count: {counter.count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
};
For scenarios where you need to use or manipulate state outside of React components, directly access the slice or global store.
import { storeManager } from "noducs";
storeManager.getState()["counter"].count += 1;
storeManager.updateGlobalState();
console.log(storeManager.getState());
Middleware lets you intercept and manipulate state updates. You can log, validate, or perform asynchronous actions during updates.
Log every state update for debugging purposes.
// middleware/logger.ts
import { Middleware } from "noducs";
export const logger: Middleware<any> = (state, updater, next) => {
console.log("🔄 Previous State:", state);
next(updater);
console.log("✅ Updated State:", state);
};
Handle asynchronous logic like delays or API calls before updating state.
// middleware/async.ts
export const asyncMiddleware: Middleware<any> = async (
state,
updater,
next
) => {
console.log("⏳ Before Async Update");
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate delay
next(updater);
console.log("✅ After Async Update");
};
Add middleware when creating a state slice to enhance functionality.
// counterState.ts
import { createSliceState } from "noducs";
import { logger } from "./middleware/logger";
import { asyncMiddleware } from "./middleware/async";
const counterSlice = createSliceState({
name: "counter",
initialState: { count: 0 },
middlewares: [logger, asyncMiddleware], // Attach middleware
});
Define the initial state when creating a slice or state.
const initialState = { count: 0 };
Use set or actions to update the state.
counterSlice.set((state) => (state.count += 1)); // Direct update
counterSlice.actions.setCount(5); // Auto-generated action
Middleware runs before state updates are applied, enabling logging, validation, or async handling.
const logger: Middleware<any> = (state, updater, next) => {
console.log("Before Update:", state);
next(updater);
};
After updates, all components or hooks using the state re-render automatically.
Feature noducs Redux Context API
Boilerplate ❌ Minimal ✅ High ✅ Moderate
<Provider>Needed ✅ Optional ✅ Yes ✅ Yes
Middleware Support ✅ Yes ✅ Yes ❌ No
Async Updates ✅ Yes ✅ Yes ❌ No
State Access ✅ Global/Direct ✅ Global ✅ Limited
Auto-Generated Actions ✅ Yes ❌ No ❌ No
-
noducs stands out with optional usage and the ability to access global state directly.
-
Auto-generated actions reduce boilerplate significantly compared to Redux.
-
Context API lacks middleware and async handling, which noducs provides out of the box.
Focus each state slice on a specific domain or feature for better scalability and maintainability.
Use middleware to log state changes, validate updates, and handle asynchronous operations.
Keep your state structure flat to simplify updates and improve performance.
Use direct global store access (storeManager) for utility functions or outside React, and for React-based state management.
No, you don’t need a . State slices are independently managed, and you can access the global store directly using storeManager. However, the is available for React-based global state management when needed.
Absolutely. noducs works with both SSR and client-side rendering. Use storeManager for SSR-friendly state access and for React components.
Yes, noducs is highly scalable. You can create multiple state slices with middleware and async support without any performance bottlenecks.
Yes, Redux DevTools is fully integrated. You can track and debug state changes effortlessly.
We’d love for you to contribute and help make noducs even better! Whether it’s fixing a bug, adding a feature, or improving the documentation, your contributions are always welcome. Here’s how you can get started:
Fork the Repository: Create your own copy of the repo to work on.
Submit a Pull Request: Once you’ve made your changes, send us a pull request. We’ll review it and work with you to get it merged.
Report Bugs or Suggest Features: Found a bug? Have an idea for a cool new feature? Let us know by creating an issue!
noducs is licensed under the MIT License.