Skip to main content

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

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:

  1. Fork the repository.
  2. Create a new branch for your feature or bugfix.
  3. Commit your changes with clear messages.
  4. Submit a pull request describing your changes.

Please ensure your code adheres to the project's coding standards and includes relevant tests.

License

MIT