Smart
Smart State Management Library
Welcome to the Smart State Management Library — a lightweight, intuitive, and powerful solution for managing state in React applications. Smart is designed to simplify state handling, providing developers with a flexible architecture that seamlessly integrates with React's ecosystem. Whether you're building small components or large-scale applications, Smart offers the tools you need to create scalable and maintainable codebases.
Table of Contents
- Smart State Management Library
Features
- Simple State Management: Manage your state with ease using the
Smart
class and React Hooks. - TypeScript Support: Fully typed with TypeScript for enhanced developer experience and type safety.
- React Integration: Seamlessly integrates with React through custom Hooks and Higher-Order Components (HOCs).
- Subscriber Notifications: Subscribe to state changes and react accordingly.
- Flexible Configuration: Customize the behavior of your Smart models as needed.
- Testing Ready: Designed with testing in mind, ensuring your state management logic is reliable.
Installation
Install the Smart library via npm or yarn:
# Using npmnpm install @bluelibs/smart
# Using yarnyarn add @bluelibs/smart
Getting Started
Creating a Smart Model
Start by creating a subclass of the Smart
class tailored to your specific state needs. For example, let's create a simple counter model.
// CounterSmart.tsximport React, { createContext } from "react";import { Smart } from "@bluelibs/smart";
type CounterState = { count: number;};
export class CounterSmart extends Smart<CounterState> { constructor() { super(); this.state = { count: 0 }; }
increment() { this.updateState({ count: this.state.count + 1 }); }
decrement() { this.updateState({ count: this.state.count - 1 }); }}
Using the useSmart
Hook
The useSmart
Hook allows your React components to access and interact with the Smart model.
// CounterComponent.tsximport React from "react";import { useSmart } from "@bluelibs/smart";import { CounterSmart } from "./CounterSmart";
const CounterComponent: React.FC = () => { const counter = useSmart(CounterSmart);
return ( <div> <p>Count: {counter.state.count}</p> <button onClick={() => counter.increment()}>Increment</button> <button onClick={() => counter.decrement()}>Decrement</button> </div> );};
export default CounterComponent;
Using the withSmart
HOC
Alternatively, you can use the withSmart
Higher-Order Component to inject the Smart model into your components.
// App.tsximport React from "react";import { withSmart } from "@bluelibs/smart";import { CounterSmart } from "./CounterSmart";import CounterComponent from "./CounterComponent";
const SmartCounter = withSmart(CounterSmart)(CounterComponent);
const App: React.FC = () => { return ( <div> <h1>Smart Counter</h1> <SmartCounter /> </div> );};
export default App;
Advanced Usage
Customizing Smart Creation
You can customize how Smart models are created by providing a factory function or setting default options.
// customFactory.tsimport { Smart, INewSmartOptions, SmartConstructor } from "@bluelibs/smart";
export function customFactory<S, U, T extends Smart<S, U>>( targetType: SmartConstructor<S, U>, config: U): T { const model = new targetType(); // Customize the model as needed return model;}
// Usageimport { useNewSmart, setDefaults } from "@bluelibs/smart";import { CounterSmart } from "./CounterSmart";import { customFactory } from "./customFactory";
setDefaults({ factory: customFactory,});
const [counter, Provider] = useNewSmart(CounterSmart, { /* config */});
Silent State Updates
Sometimes you might want to update the state without notifying subscribers immediately. Use the silent
option for this purpose.
counter.setState({ count: 10 }, { silent: true });// Subscribers won't be notifiedcounter.inform(); // Manually inform subscribers when ready
Testing
Smart is designed to work seamlessly with testing frameworks like Jest and React Testing Library. Below is an example of how to test a component using the Smart
library.
// CounterComponent.test.tsximport React from "react";import { render, fireEvent } from "@testing-library/react";import { CounterSmart } from "./CounterSmart";import { useSmart, createSmart, withSmart } from "@bluelibs/smart";import "@testing-library/jest-dom/extend-expect";
// Define a test componentconst CounterComponent: React.FC = () => { const counter = useSmart(CounterSmart);
return ( <div> <p data-testid="count">Count: {counter.state.count}</p> <button onClick={() => counter.increment()}>Increment</button> <button onClick={() => counter.decrement()}>Decrement</button> </div> );};
// Create a wrapper with the Providerconst Wrapper: React.FC = () => { const [model, Provider] = createSmart(CounterSmart); return ( <Provider> <CounterComponent /> </Provider> );};
test("CounterComponent increments and decrements", () => { const { getByText, getByTestId } = render(<Wrapper />);
const countText = getByTestId("count"); const incrementButton = getByText("Increment"); const decrementButton = getByText("Decrement");
expect(countText).toHaveTextContent("Count: 0");
fireEvent.click(incrementButton); expect(countText).toHaveTextContent("Count: 1");
fireEvent.click(decrementButton); expect(countText).toHaveTextContent("Count: 0");});
Jest Configuration
Ensure your Jest configuration is set up to handle React and TypeScript:
// jest.config.jsmodule.exports = { preset: "ts-jest", testEnvironment: "jsdom", setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],};
// jest.setup.tsimport "@testing-library/jest-dom";
TypeScript Configuration
Update your tsconfig.json
to include the necessary types:
{ "compilerOptions": { "target": "ES6", "module": "commonjs", "jsx": "react", "moduleResolution": "node", "esModuleInterop": true, "strict": true, "types": ["jest", "@testing-library/jest-dom"], "skipLibCheck": true }, "exclude": ["node_modules", "**/*.test.tsx"]}
API Reference
Smart Class
The Smart
class is the core of the library, providing state management and subscriber notifications.
Class: Smart
abstract class Smart<StateModel = any, Config = any> { public state: StateModel; public config: Config;
constructor(config?: Config);
async init(): Promise<void>;
async destroy(): Promise<void>;
setState(newState: StateModel, options?: SetStateOptions): void;
updateState(update: Partial<StateModel>, options?: SetStateOptions): void;
subscribe(subscriber: SmartSubscriber<StateModel>): void;
unsubscribe(subscriber: SmartSubscriber<StateModel>): void;
static getContext<T extends Smart>(): React.Context<T>;
getSubscriberCount(): number; // For testing purposes}
Types
SmartSubscriber
type SmartSubscriber<StateModel> = ( oldState: StateModel | undefined, newState: StateModel) => void;
SetStateOptions
type SetStateOptions = { silent?: boolean;};
Hooks and HOCs
useSmart Hook
Allows components to access the Smart model from context and re-render on state changes.
function useSmart<T extends Smart>(modelClass: { getContext(): React.Context<T>;}): T;
useNewSmart Hook
Initializes the Smart model and provides a Provider component.
function useNewSmart<T extends Smart>(modelClass: { new (): T; getContext(): React.Context<T>;}): [T, FC];
withSmart HOC
Wraps a component with the Smart Provider.
function withSmart<T extends Smart>(modelClass: { new (): T; getContext(): React.Context<T>;}): <P extends object>(Component: React.ComponentType<P>) => FC<P>;
Examples
Simple Counter Example
CounterSmart.tsx
import React, { createContext } from "react";import { Smart } from "@bluelibs/smart";
type CounterState = { count: number;};
export class CounterSmart extends Smart<CounterState> { constructor() { super(); this.state = { count: 0 }; }
increment() { this.updateState({ count: this.state.count + 1 }); }
decrement() { this.updateState({ count: this.state.count - 1 }); }}
CounterComponent.tsx
import React from "react";import { useSmart } from "@bluelibs/smart";import { CounterSmart } from "./CounterSmart";
const CounterComponent: React.FC = () => { const counter = useSmart(CounterSmart);
return ( <div> <p>Count: {counter.state.count}</p> <button onClick={() => counter.increment()}>Increment</button> <button onClick={() => counter.decrement()}>Decrement</button> </div> );};
export default CounterComponent;
App.tsx
import React from "react";import { createSmart } from "@bluelibs/smart";import { CounterSmart } from "./CounterSmart";import CounterComponent from "./CounterComponent";
const App: React.FC = () => { const [counterModel, CounterProvider] = createSmart(CounterSmart);
return ( <CounterProvider> <h1>Smart Counter</h1> <CounterComponent /> </CounterProvider> );};
export default App;
Higher-Order Component Example
EnhancedCounter.tsx
import React from "react";import { withSmart } from "@bluelibs/smart";import { CounterSmart } from "./CounterSmart";
interface EnhancedCounterProps { label: string;}
const EnhancedCounter: React.FC<EnhancedCounterProps> = ({ label }) => { const counter = useSmart(CounterSmart);
return ( <div> <h2>{label}</h2> <p>Count: {counter.state.count}</p> <button onClick={() => counter.increment()}>Increment</button> <button onClick={() => counter.decrement()}>Decrement</button> </div> );};
export default withSmart(CounterSmart)(EnhancedCounter);
App.tsx
import React from "react";import EnhancedCounter from "./EnhancedCounter";
const App: React.FC = () => { return ( <div> <h1>Smart Counter with HOC</h1> <EnhancedCounter label="My Counter" /> </div> );};
export default App;
Contributing
Contributions are welcome! Whether it's bug fixes, new features, or improving documentation, your input is valuable. To contribute:
- Fork the repository.
- Create a new branch for your feature or bugfix.
- Commit your changes with clear messages.
- Submit a pull request describing your changes.
Please ensure your code adheres to the project's coding standards and includes relevant tests.