Achieve fine grained reactivity and super fast UI updates in React with Legend-state
Tags: react, tailwindcss, legend, vite
Published: 2023-07-26 09:28:30

## Introduction
Legend state is a super fast state management library that is built on top of **observables**.

## Features

- Better Performance
- Easy to use
- Fine grained reactivity
- Built-in persistent
- Better memory usage
- No Boilerplate
- Lightweight (~4kb)
- Better DX (Developer Experience)


Legend-state Observables use **Proxy** in a unique way which is really fast  and it doesn't modify the underlying data.
In optimized mode for an array only re render the changed element instead of the whole array.
Legend-state render component only once and update needed part of ui yes it feels like the **Solid.js.**

If we look into the current mental model of React when we update the state it rerender the component and then execute side effects.

**State changed** =>  **Rerender component** =>  **Execute side effect**

But legend mental model skip the second step from React mental model and  execute your side effect directly after state change.

**State changed** =>  **Execute side effect**

If it comes to optimize react app we jump into applying `useCallback`, `useMemo` to prevent from unusual rerender but in legend component render only once you do not required these react hooks.

You can easily replace `useState` and `useReducer` with `useObservable` hook that keep track of deep object and notify listeners when state change and instead of rerendering the whole component it will only updates the needed part of UI.

In the same way you can replace `useEffect` with `useObserve` so no need to maintain the dependency array.


Okay now we are going to develop a todo app that will utilize legend-state and natural fine grained reactivity.

Initialize new project with vite react-ts and follow the terminal instructions.

```sh
 pnpm create vite todo --template react-ts

```

Setup tailwindcss

```
pnpm i -D tailwindcss postcss autoprefixer
pnpx tailwindcss init -p

```

`tailwind.config.js`

```js
import daisyui from "daisyui"
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

```


`src/index.css`

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

```

Add daisyui plugins to make our component beautiful with fewer classes.

```
npm i -D daisyui@latest

```

`tailwind.config.js`

```js

module.exports = {
  //...
  plugins: [require("daisyui")],
}

```

Define Todo type and Status enum

`types/todo.ts`

```ts
export enum Status {
  Initialized = "initialized",
  Progress = "progress",
  Done = "done",
}

export type Todo = {
  id: string;
  text: string;
  status: Status;
};

```

Create global store using legend state `observable`

`src/store/todo.ts`

```ts
import { observable } from "@legendapp/state";
import { Todo } from "../types/todo";

export const state = observable<{ todos: Todo[] }>({
  todos: [],
});

```

Update `main.tsx`

```ts

import { enableReactUse } from "@legendapp/state/config/enableReactUse";

enableReactUse(); // This adds the use() function to observables

// ...
// ...


```
Update `App.tsx`

```ts

import TodoItem from "./components/todo-item";
import { state } from "./store/todo";
import { Status } from "./types/todo";
import { For, Reactive, useObservable } from "@legendapp/state/react";
import { enableReactComponents } from "@legendapp/state/config/enableReactComponents";

enableReactComponents();
export default function App() {
  const input = useObservable("");
  const handleAddTodo = () => {
    state.todos.push({
      id: crypto.randomUUID(),
      status: Status.Initialized,
      text: input.get(),
    });
    input.set("");
  };

  return (
    <div className="min-h-screen h-full bg-base-100 py-8">
      <div className="max-w-xl mx-auto">
        <section className="flex flex-col gap-y-2 items-center  text-center">
          <h1 className="font-bold text-3xl">Todo</h1>
          <p className="text-xl">
            Todo web app build with React | Vite | Legend State | Tailwindcss |
            Daisyui
          </p>
        </section>

        <section className="mt-8 flex items-center gap-2">
          <Reactive.input
            autoFocus
            placeholder="Start typing..."
            $value={input}
            className="input input-bordered w-full"
          />
          <button onClick={handleAddTodo} className="btn btn-primary">
            Add
          </button>
        </section>

        <section className="mt-8">
          <div className="grid grid-cols-1 gap-2">
            <For each={state.todos}>
              {(todo) => <TodoItem todo={todo.get()} />}
            </For>
          </div>
        </section>
      </div>
    </div>
  );
}


```

Legend-State provides reactive versions of all platform components with reactive props to use these components first enable it using `enableReactComponents()`

`Reactive.input` add  two-way binding to the value, so that the observable is always in sync with the displayed value of the element.

`<For>` component is optimized for rendering arrays of observable objects so that they are extracted into a separate tracking context and don't re-render the parent.

Using `.get()` we can get value from Observable and `.set()` trigger the state update.


Create `src/components/todo-item.tsx`

```tsx

import { state } from "../store/todo";
import { Status, Todo } from "../types/todo";
import { Reactive } from "@legendapp/state/react";
import TodoStatus from "./todo-status";

export default function TodoItem({ todo }: { todo: Todo | undefined }) {
  const handleRemoveTodo = () => {
    state.todos.set((todos) => todos.filter((t) => t.id !== todo?.id));
  };
  return (
    <Reactive.article
      $className={
        todo?.status === Status.Progress
          ? "border-warning card card-bordered"
          : todo?.status === Status.Done
          ? "border-error card card-bordered"
          : "card card-bordered"
      }
    >
      <div className="card-body p-4">
        {todo?.text}

        <div className="card-actions flex gap-2">
          <TodoStatus id={todo?.id} status={todo?.status} />

          <button onClick={handleRemoveTodo} className="btn btn-error btn-sm">
            Remove
          </button>
        </div>
      </div>
    </Reactive.article>
  );
}


```
 


`src/components/todo-status.tsx`

```tsx
import { state } from "../store/todo";
import { Status } from "../types/todo";

export default function TodoStatus({
  id,
  status,
}: {
  id: string | undefined;
  status: Status | undefined;
}) {
  const handleChangeStatus = (status: Status) => {
    state.todos.set((todos) =>
      todos.map((todo) => {
        if (todo.id === id) {
          return {
            ...todo,
            status,
          };
        }
        return todo;
      })
    );
  };
  return (
    <select
      className="select flex-1 select-sm select-bordered"
      value={status}
      onChange={(e) => handleChangeStatus(e.target.value as Status)}
    >
      <option value={Status.Initialized}>Initialized</option>
      <option value={Status.Progress}>Progress</option>
      <option value={Status.Done}>Done</option>
    </select>
  );
}


```

You can go through legend-state documentation [here](https://legendapp.com/open-source/state/)
Full source code is available on github

{% github harshmangalam/reactjs-legend-state-daisyui-todo %}

Also i have developed  for nextjs using nextjs-13 app router | shadcn-ui | legend-state

{% github harshmangalam/nextjs-legend-state-shadcn-ui-todo %}