State Management in React With App’s Digest
Tags: react, typescript, javascript, opensource
Published: 2023-01-06 03:14:49

When the complexity of an application is such that local component state is not feasible, we need to embrace some form of state management that fits our architecture and allows our application to scale while preserving a good code readability.

Redux provides a good scalability, unfortunately it comes at the cost of too much code boilerplate. This has created a good opportunity for other simple state management libraries, such as Recoil, Zustand, Jōtai and [App's Digest](https://www.npmjs.com/package/apps-digest) to thrive.

App's Digest has the smallest bundle size and offers an API that allows your code to be decoupled, portable, and testable, regardless of the UI framework your application uses.

This guide will give you a comprehensive look at the App's Digest library and prepare you to use it in your next project.

## How App's Digest works

App's Digest is an atomic state management. Unlike Redux and Zustand, where data is stored in global storage outside the component tree. In App's Digest, data is stored in centralized locations called stores, with units of state called values that your application can subscribe to.

![how apps digest works](https://raw.githubusercontent.com/martyroque/emrock-blog/22a90734b18b9642bd4012c11ced3dbf7ea6db98/public/images/apps_digest_flow.jpeg "How App's Digest works")

App's Digest stores data in small, independent and updatable values that combine to form a more complex values (computed values), and it only triggers strictly-needed, isolated updates for computations (e.g. React components) subscribed to values. All this without the need to create a large number of providers or use derived states selectors. Instead, App's Digest gives you access to a particular state anywhere in your application and manipulating it to your preference.

## Building an app using App's Digest

Let’s build a TODO app where you will learn how to create, update, and delete tasks.

### Create a new React app

Let's use the `create-react-app` library to create a new React application. From the terminal, we will run:

```sh
npx create-react-app todo-app
```

The above command will bootstrap the application and install React dependencies. Once it completes, navigate to the folder:

```sh
cd todo-app
```

And install App's digest:

```sh
npm install apps-digest
```

### Creating a Store

Now let's create a basic representation of the store using a class. You can place all your stores in `src/stores`. We will name this file `TodoStore.ts`.

```ts
import { AppsDigestStore } from "apps-digest";

class TodoStore extends AppsDigestStore {
}

export default TodoStore;
```

### Creating Store Values

Our store needs values it can update and keep track of. So, let's import `AppsDigestValue` and create our first store value, our TODOs:

```ts
import { AppsDigestStore, AppsDigestValue } from "apps-digest";

export type Todo = {
  id: string;
  text: string;
  done: boolean;
  createdAt: number;
};

export type TodoList = Todo[];

class TodoStore extends AppsDigestStore {
  // our store value with initial value as empty array
  public todos = new AppsDigestValue<TodoList>([]);
}

export default TodoStore;
```

### Creating our components

Create a components folder in the `src` folder.

#### Add TODO

In the components folder, create a file called `TodoAdd.tsx` with an input and a button. This component is where our users will add new TODOs.

```tsx
const TodoAdd = () => {
  return (
    <div>
      <input placeholder="New TODO" />
      <button>Add</button>
    </div>
  );
};

export { TodoAdd };
```

#### TODO Item

Now, let's create a `TodoItem.tsx` with a checkbox, an input and a button. This component will render individual TODOs.

```tsx
const TodoItem = () => {
  return (
    <div>
      <input type="checkbox" />
      <input />
      <button>Delete</button>
    </div>
  );
};

export { TodoItem };
```

#### TODO List

Next, we need to create a component that will display all our TODOs. In the components folder, create another TSX file called `TodoList.tsx` and render the list of `todos`.

```tsx
import { TodoItem } from "./TodoItem";

const TodoList = () => {
  const todos = []; // Empty for now
  return (
    <div>
      {todos.map((todo) => (
        <TodoItem key={todo.id} {...todo} />
      ))}
    </div>
  );
};

export { TodoList };
```

### Adding TODOs

Now, let's create our `addTodo` setter method to our store. Setter methods in a Store are optional, but it is a good practice to follow the single-responsibility pattern, which ensures a single source of truth and update.

```ts
class TodoStore extends AppsDigestStore {
  public todos = new AppsDigestValue<TodoList>([]);
  
  public addTodo(text: string) {
    // get current value from state
    const currentTodos = this.todos.currentValue();

    // publish the new state
    this.todos.publish([
      ...currentTodos,
      {
        id: nanoid(), // we're using a nanoid for ID generation
        text,
        done: false,
        createdAt: +new Date()
      }
    ]);
  }
}
```

Once our setter is in place, let's consume it in our `TodoAdd.tsx` component. To get our store in a component, we will use the hook `useAppsDigestStore`. We will also use `useState` to control de text input value.

```tsx
import { useState } from "react";
import { useAppsDigestStore } from "apps-digest";

import TodoStore from "../stores/TodoStore";

const TodoAdd = () => {
  // Get our store
  const todoStore = useAppsDigestStore(TodoStore);
  
  const [value, setValue] = useState("");

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  const onAddClick = () => {
    if (value) {
      // Call our setter
      todoStore.addTodo(value);
      
      // Reset the input value
      setValue("");
    }
  };
  
  return (
    <div>
      <input 
        placeholder="New TODO"
        value={value} 
        onChange={onInputChange}
      />
      <button onClick={onAddClick}>Add</button>
    </div>
  );
};
```

### Displaying TODO list

Once the code for adding TODOs is in place, we can now display our TODO list by accessing store values within a component. For this, we will use the hook `useAppsDigestValue`.

```tsx
import { useAppsDigestStore, useAppsDigestValue } from "apps-digest";

import TodoStore from "../stores/TodoStore";

const TodoList = () => {
  // Get our store
  const todoStore = useAppsDigestStore(TodoStore);
  
  // Access the todos value
  const todos = useAppsDigestValue(todoStore.todos);
  
  return (
    <div>
      {todos.map((todo) => (
        <TodoItem key={todo.id} {...todo} />
      ))}
    </div>
  );
};
```

Let's give this a quick test by adding the `TodoAdd.tsx` and the `TodoList.tsx` components to our `App.tsx`:

```tsx
import { TodoAdd } from "./components/TodoAdd";
import { TodoList } from "./components/TodoList";

export default function App() {
  return (
    <div className="App">
      <h1>State Management in React with App's Digest</h1>
      <h2>My TODO list</h2>
      <TodoList />
      <TodoAdd />
    </div>
  );
}
```

![adding todos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c90vg7skowfztbpyect7.gif)

Very easy, right? Now, if you'd like, take some time to add some styling. Up next, we will see how to update, toggle and remove TODOs.

### Updating TODOs

To allow our users update the text of their TODOs, we will create a new setter method in our `TodoStore` called `updateTodo`.

```ts
class TodoStore extends AppsDigestStore {
  public todos = new AppsDigestValue<TodoList>([]);
  
  public addTodo(text: string) {}
  
  public updateTodo(id: string, text: string) {
    const currentTodos = this.todos.currentValue();
    // find the TODO to update
    const todo = currentTodos.find((todo) => todo.id === id);
    
    this.todos.publish([
      // filter out the other TODOs
      ...currentTodos.filter((todo) => todo.id !== id),
      // To produce a new state (always remember to publish new state)
      { ...todo, text }
    ]);
  }
}
```

### Toggling TODOs

Let's create another setter method that will toggle a given TODO's `done` flag. We will name it `toggleTodo`.

```ts
class TodoStore extends AppsDigestStore {
  public todos = new AppsDigestValue<TodoList>([]);
  
  public addTodo(text: string) {}
  
  public updateTodo(id: string, text: string) {}
  
  public toggleTodo(id: string) {
    const currentTodos = this.todos.currentValue();
    const todo = currentTodos.find((todo) => todo.id === id);
    
    this.todos.publish([
      ...currentTodos.filter((todo) => todo.id !== id),
      { ...todo, done: !todo.done }
    ]);
  }
}
```

### Removing TODOs

This will be another setter method within our store, `removeTodo`:

```ts
class TodoStore extends AppsDigestStore {
  public todos = new AppsDigestValue<TodoList>([]);
  
  public addTodo(text: string) {}
  
  public updateTodo(id: string, text: string) {}
  
  public toggleTodo(id: string) {}
  
  public removeTodo(id: string) {
    const currentTodos = this.todos.currentValue();

    this.todos.publish(currentTodos.filter((todo) => todo.id !== id));
  }
}
```

### Putting all together

With all our update, toggle and remove methods created, let's now consume them in our `TodoItem.tsx` component. Remember, we use `useAppsDigestStore ` to access our store from React components.

```tsx
import { useAppsDigestStore } from "apps-digest";

import TodoStore, { Todo } from "../stores/TodoStore";

const TodoItem = ({ id, text, done }: Todo) => {
  const todoStore = useAppsDigestStore(TodoStore);

  const onCheckboxChange = () => {
    todoStore.toggleTodo(id);
  };

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    todoStore.updateTodo(id, e.target.value);
  };

  const onDeleteClick = () => {
    todoStore.removeTodo(id);
  };

  return (
    <div>
      <input type="checkbox" onChange={onCheckboxChange} checked={done} />
      <input value={text} onChange={onInputChange} />
      <button onClick={onDeleteClick}>Delete</button>
    </div>
  );
};
```

### Computed Values

Now, as you noticed, the TODO list order changes as you update any given TODO, which is a bit annoying. So let's fix this by using the `createdAt` property of our TODOs to order them.

To achieve this we will use a great App's Digest feature, computed values, which allows us to consume store values, compute them in a callback and produce a single result.

So, let's create our `orderedTodos` computed value.

```ts
class TodoStore extends AppsDigestStore {
  public todos = new AppsDigestValue<TodoList>([]);
  public orderedTodos = this.computedValue([this.todos], (todos) => {
    return todos.sort((a, b) => (a.createdAt > b.createdAt ? 1 : -1));
  });
  
  // ...
}
```

`computedValue` method accepts an array of store values, and a callback we expect to receive these to be computed or derived.

Cool, let's use our new computed value in our TODO list component.

```tsx
const TodoList = () => {
  // Get our store
  const todoStore = useAppsDigestStore(TodoStore);
  
  // Access the new computed orderedTodos value
  const todos = useAppsDigestValue(todoStore.orderedTodos);
  
  return (
    <div>
      {todos.map((todo) => (
        <TodoItem key={todo.id} {...todo} />
      ))}
    </div>
  );
};
```

And that's it! Our TODOs will stay in order in which they were created, no matter the updates.

![updating todos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b4xx4m1xystsg4w7hs9.gif)

## Conclusion

The App's Digest atomic and zero-provider approach to state management is newer than Redux and Zustand, but it has been well-received by the developer community. App's Digest has proven reliable in small to large-size projects, and it’s without a doubt that it is an equal competitor to any state management library out there.

## Resources

[Final TODO app code](https://codesandbox.io/s/apps-digest-react-845l55)
[App's Digest Github](https://github.com/martyroque/apps-digest)
[App's Digest NPM Package](https://www.npmjs.com/package/apps-digest)