Service oriented state management with lamp-luwak

Due to the fact that React provides amazing capabilities for working with display, you can only focus on organizing the application logic and the semantics of the code that describes how to work with data. Those. choosing a state management library, the choice of the style of the future code base takes place.


In this article, we will consider a service-store-based approach implemented through a library lamp-luwak.


lamp-luwak image


TL; DR Whoever wants to get started writing code soon can skip to the next paragraph.


All the logic inside your application is divided into services, each service contains a single side, the change of which notifies subscribers. The service also contains logic functions, where you can access other services and rewrite the immutable side, these functions can be asynchronous and contain side effects.


lamp-luwak , . , React useProvide useSubscribe . , , .


, - . , embedded . .


subscribe. .



, todo- :


  • ;
  • / ;
  • /.


todos create-react-app


npx create-react-app todos --template typescript --use-npm
# or
yarn create react-app todos --template typescript

lamp-luwak todos


npm i --save lamp-luwak
# or
yarn add lamp-luwak


, React - , - .


— -, , .
-, store.


class Todos {
  store = [ /*...*/ ];
  // ...

store . .


class TodoCounters {
  todo = provide(Todo);
  // ...

provide , , Todo.
React


const List = () => {
  const todo = useProvide(Todo);
  // ...
};

-. React useProvide, — - , Todo. , .


— , , store provide useProvide / , .


, List Todo.



services, React components .


sr/
  components/       //   React  
    Counters.tsx    //     
    Input.tsx       //     
    Task.tsx        // 
    List.tsx        //  
  services/         //   
    Todo/
      Task.ts       // 
    Todo.ts         //   
    TodoCounters.ts //      
  App.tsx           //   


2- . :


  • Todo — , add ;
  • TodoCounters — .

Todo , Task, .. , , . , , . , Date, Map, Set. ( SSR) , UI. , . , Task toggle, .


Task, , create, -, - .


// Todo.ts
import { create } from 'lamp-luwak';
import { Task } from './Todo/Task';

export class Todo {
  store = [
    create(Task, { id: 1, label: 'Cook the dinner', completed: false }),
    create(Task, { id: 2, label: 'Cook the breakfast', completed: true })
  ]
  add(label: string) {
    this.store = this.store.concat(
      create(Task, { id: Date.now(), label, completed: false })
    );
  }
}

Todo. store Task, create. add, , Task. id, Date.now.


// Todo/Task.ts
import { subscribe, modify, action } from 'lamp-luwak';

type Store = {
  id: number,
  label: string,
  completed: boolean
}

export const TaskChanged = action();

export class Task {
  store: Store;
  constructor(store: Store) {
    this.store = store;
    subscribe(this, TaskChanged);
  }
  toggle() {
    modify(this).completed = !this.store.completed;
  }
}

Task, . : id , label completed. toggle, , modify, , .


TaskChanged, action.


— , . , — dispatch lamp-luwak.


, , TaskChanged Task. subscribe, — , .. , — , . TaskChanged, .


// TodoCounters.ts
import { provide, subscribe } from 'lamp-luwak';
import { Todo } from './Todo';
import { TaskChanged } from './Todo/Task';

export class TodoCounters {
  todo = provide(Todo);
  store = {
    active: 0,
    completed: 0
  }
  constructor() {
    subscribe(this.todo, this.calculate, this);
    subscribe(TaskChanged, this.calculate, this);
    this.calculate();
  }
  calculate() {
    const items = this.todo.store;
    const completed = items.filter(item => item.store.completed).length;
    const active = items.length - completed;
    this.store = { completed, active };
  }
}

TodoCounters, . 2- :


  • Todo, , .
  • TaskChanged , - Task, completed, .

calculate.


:


service architecture image



App.tsx, .


List — .


// List.tsx
import React from 'react';
import { useProvide } from 'lamp-luwak';
import { Todo } from '../services/Todo';
import { Task } from './Task';

export const List = () => {
  const todo = useProvide(Todo);
  const items = todo.store;
  if (items.length === 0) return null;
  return (
    <ul>
      {items.map(item => (
        <Task task={item} key={item.store.id} />
      ))}
    </ul>
  )
};

Todo, useProvide, List, , , . , .


Task — .


// Task.tsx
import React, { FC } from 'react';
import { useSubscribe } from 'lamp-luwak';
import { Task as TaskClass } from '../services/Todo/Task';

export const Task: FC<{ task: TaskClass }> = ({ task }) => {
  useSubscribe(task);
  const { label, completed } = task.store;
  return (
    <li>
      <input
          className="toggle"
          type="checkbox"
          checked={completed}
          onChange={() => task.toggle()}
        />
      <span style={{
        textDecoration: completed ? 'line-through' : 'none'
      }}>
        {label}
      </span>
    </li>
  )
};

Task, , useSubscribe, . , toggle .


Counters — .


// Counters.tsx
import React from 'react';
import { useProvide } from 'lamp-luwak';
import { TodoCounters } from '../services/TodoCounters';

export const Counters = () => {
  const { active, completed } = useProvide(TodoCounters).store;
  return (
    <>
      <div>Active: {active}</div>
      <div>Completed: {completed}</div>
    </>
  )
};

, useProvide . , TodoCounters.


Input — .


add Todo. , Todo, React useState.


// Input.tsx
import React, { useState } from 'react';
import { useProvide } from 'lamp-luwak';
import { Todo } from '../services/Todo';

export const Input = () => {
  const [text, setText] = useState('Cook the lunch');
  const todo = useProvide(Todo);
  const add = () => {
    todo.add(text);
    setText('');
  };

  return (
    <>
      <input
        onChange={(e) => setText(e.target.value)}
        value={text}
        autoFocus
        onKeyDown={(event: any) => {
          if (event.keyCode === 13) add();
        }}
      />
      <button onClick={add}>Add</button>
    </>
  );
};

App.tsx, src , .


// App.tsx
import React from 'react';
import { Input } from './components/Input';
import { List } from './components/List';
import { Counters } from './components/Counters';

const App = () => (
  <>
    <Input />
    <List />
    <Counters />
  </>
);

export default App;

Enjoy! .


lamp-luwak, , . .



, .



, , , .
.




All Articles