reca
TypeScript icon, indicating that this package has built-in type declarations

1.1.13 • Public • Published

Codacy Badge npm version Contributor Covenant GitHub license

ReCA - React Clean Architecture state manager

Created at the intersection of Functional style and OOP technologies. It is based on the simplicity of the functional style of the view, enriched with OOP technologies for writing business logic. Perfect for beginner developers and complex enterprise applications

Features

  • Microstores - calculations state of components don't affect to other components, small CPU usage for update states,
  • Direct Functions Call - don't need heavy CPU utilization for search function in reducer, just call the function directly,
  • No Boilerplate - write only business code without those debt,
  • Dependency Injection - override any part of your application for unit test or other customer,
  • Microfrontend - perfect support microfrontends out the box without any boilerplates,
  • Simple Data Flow - don't need search functions call chain for debug your reducers,
  • Code Organization - structures the code easily even for large enterprise applications,
  • Extra Small Size - only 1kb of minified code.

Why not Redux or Flux?

  • Monostore - as the application grows, the cost of maintaining a monostore greatly exceeds the useful work.
  • Reducers - a large number of reducers makes you spend a lot of time searching for the necessary function.
  • Architecture problem - forces you to use tons of additional packages to solve problems, such as saga, thunk, toolkit and many others.

Instalation

npm:

npm install reca

yarn

yarn add reca

Examples

Example AutoStore

Create your Store by inheriting from AutoStore, and use it in a component via useStore hook.

// todo.store.ts
import {AutoStore} from "reca";
import type {FormEvent} from "react";

export class ToDoStore extends AutoStore {

    public currentTodo: string = "";

    public todos: string[] = [];

    public handleAddTodo (): void {
        this.todos.push(this.currentTodo);
    }

    public handleDeleteTodo (index: number): void {
        this.todos.splice(index, 1);
    }

    public handleCurrentEdit (event: FormEvent<HTMLInputElement>): void {
        this.currentTodo = event.currentTarget.value;
    }

}


// todo.component.ts
import {useStore} from "reca";
import {ToDoStore} from "../stores/todo.store";

export const ToDoComponent = (): JSX.Element => {
    const store = useStore(ToDoStore);

    return (
        <div className="todos">
            <div className="todos-list">
                {
                    store.todos.map((todo, index) => (
                        <div className="todo">
                            {todo}

                            <button
                                className="todo-delete"
                                onClick={() => store.handleDeleteTodo(index)}
                                type="button"
                            >
                                X
                            </button>
                        </div>
                    ))
                }
            </div>

            <div className="todos-input">
                <input
                    onInput={store.handleCurrentEdit}
                    value={store.currentTodo}
                />

                <button
                    onClick={store.handleAddTodo}
                    type="button"
                >
                    add
                </button>
            </div>
        </div>
    );
};

Example low-level Store

Also, if you need uncompromising performance, you can use the low-level Store. But you will need to start redrawing manually using the this.redraw() method. Also you must pass arrow function to all used HTMLElement events, such as onClick.

// todo.store.ts
import {Store} from "reca";
import type {FormEvent} from "react";

export class ToDoStore extends Store {

    public currentTodo: string = "";

    public todos: string[] = [];

    public handleAddTodo (): void {
        this.todos.push(this.currentTodo);
        this.redraw();
    }

    public handleDeleteTodo (index: number): void {
        this.todos.splice(index, 1);
        this.redraw();
    }

    public handleCurrentEdit (event: FormEvent<HTMLInputElement>): void {
        this.currentTodo = event.currentTarget.value;
        this.redraw();
    }
}


// todo.component.ts
import {useStore} from "reca";
import {ToDoStore} from "../stores/todo.store";

export const ToDoComponent = (): JSX.Element => {
    const store = useStore(ToDoStore);

    return (
        <div className="todos">
            ...

            <div className="todos-input">
                <input
                    onInput={() => store.handleCurrentEdit()}
                    value={store.currentTodo}
                />

                <button
                    onClick={() => store.handleAddTodo()}
                    type="button"
                >
                    add
                </button>
            </div>
        </div>
    );
};

Example using DI

This example demonstrates the simplicity of the business logic and the simplified principles of code organization according to the Clean Architecture methodology. The example is simplified for readme, but following the same principles you can organize a full-fledged Clean Architecture. Through the service constructor, you can pass other DI dependencies, such as Repository, Provider, and others.

// SpaceXCompanyInfo.ts
export class SpaceXCompanyInfo {

    public name: string = "";

    public founder: string = "";

    public employees: number = 0;

    public applyData (json: object): this {
        Object.assign(this, json);
        return this;
    }

}


// SpaceXService.ts
import {reflection} from "first-di";
import {SpaceXCompanyInfo} from "../models/SpaceXCompanyInfo";

@reflection
export class SpaceXService {

    public async getCompanyInfo (): Promise<SpaceXCompanyInfo> {
        const response = await fetch("https://api.spacexdata.com/v3/info");
        const json: unknown = await response.json();

        // ... and manies manies lines of logics

        if (typeof json === "object" && json !== null) {
            return new SpaceXCompanyInfo().applyData(json);
        }
        throw new Error("SpaceXService.getCompanyInfo: response object is not json");
    }

}


// SpaceXStore.ts
import {reflection} from "first-di";
import {AutoStore} from "reca";
import {SpaceXCompanyInfo} from "../models/SpaceXCompanyInfo.js";
import {SpaceXService} from "../services/SpaceXService.js";

@reflection
export class SpaceXStore extends AutoStore {

    public companyInfo: SpaceXCompanyInfo = new SpaceXCompanyInfo();

    public constructor (
        private readonly spaceXService: SpaceXService,
        // private readonly logger: Logger
    ) {
        super();
    }

    public activate (): void {
        this.fetchCompanyInfo();
    }

    private async fetchCompanyInfo (): Promise<void> {
        try {
            this.companyInfo = await this.spaceXService.getCompanyInfo();
        } catch (error) {
            // Process exceptions, ex: this.logger.error(error.message);
        }
    }

}


// SpaceXComponent.tsx
import {useStore} from "reca";
import {SpaceXStore} from "../stores/SpaceXStore.js";

export const TestStoreComponent = (): JSX.Element => {
    const store = useStore(SpaceXStore);

    return (
        <div>
            <p>
                Company:
                {" "}

                {store.companyInfo.name}
            </p>

            <p>
                Founder:
                {" "}

                {store.companyInfo.founder}
            </p>
        </div>
    );
};

Support and Documentation

Discord server: click here

Wiki: click here

License

ReCA is MIT licensed.

Readme

Keywords

Package Sidebar

Install

npm i reca

Weekly Downloads

1

Version

1.1.13

License

MIT

Unpacked Size

30.6 kB

Total Files

24

Last publish

Collaborators

  • labeg