Create Todo list app with Elf & React
Tags: react, rxjs, redux, javascript
Published: 2022-01-15 11:26:15

---
title: Create Todo list app with Elf & React
published: true
description: React Todo list app, with Elf state management library,
tags: react, rxjs, redux, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zn9jfi3g1skmq4pibjk1.png
---

#Introduction
There are many libs for state management. But Elf (https://ngneat.github.io/elf/) have a good point than other.
> 🤘 It's independent, no bind to FW, you can use everywhere. Share the same class to many projects 😍

Here we are going to see how to create todo app with setup all logic into 1 file (elf repository)

##Demo
Source code : [https://github.com/workfel/react-todo-elf](https://github.com/workfel/react-todo-elf)
Demo : https://workfel.github.io/react-todo-elf/
 

##Installation
- Use `create-react-app` and tailwindcss. https://tailwindcss.com/docs/guides/create-react-app

- Add `elf` lib
`npm i —save @ngneat/elf @ngneat/elf-entities `

- Add `npm i --save @ngneat/use-observable` to use custom hook with Observable .

##Structure
Create folder into `src`
- components ("dumb")
- containers ("smart" components)
- infrastructures (manage context app)
- repository (todo repo)

##Repository
Start by creating skeleton of repository into `repository/todo.repository.ts`. Will be responsible for the logic of app.
```ts
// datas models
// repository/todo.repository.ts
export interface Todo {
  id: string;
  name: string;
  completed: boolean;
}

export interface VisibilityFilterProps {
  filter: 'active' | 'completed' | 'all';
}

```

We describe all we can do with the app.
```ts
// repository/todo.repository.ts
export interface TodoRepository {
  todos$: Observable<Todo[]>;

  addTodo(text: Todo['name']): void;

  markAsComplete(id: string): void;

  removeTodo(id: string): void;

  markAsActive(id: string): void;

  updateFilter(type: VisibilityFilterProps['filter']): void;
}
```

Now create the `state` of app. Contains list of `Todo` with  `withEntities<Todo>()` and props `filter` to manage the items to display `withProps<VisibilityFilterProps>({ filter: 'all' })`
```ts 
// repository/todo.repository.ts
const { state, config } = createState(
  withProps<VisibilityFilterProps>({ filter: 'all' }),
  withEntities<Todo>(),
);
```

###Create store into repo class
We will name `todos` the name of store and pass `state` & `config` previously created.
```ts
// repository/todo.repository.ts
export class TodoRepositoryElf implements TodoRepository {
  private todosStore = new Store({ name: 'todos', state, config });
  todos$: Observable<Todo[]>;

  addTodo(text: Todo['name']): void {
  }

  markAsActive(id: string): void {
  }

  markAsComplete(id: string): void {
  }

  removeTodo(id: string): void {
  }

  updateFilter(type: VisibilityFilterProps['filter']): void {
  }

}
```

## Add context
Create context `TodoContext` will permit to access on repository everywhere on app by using hook `useContext`
```ts 
// infrastructure/todo.context.provider.ts
export interface TodoContextInterface {
  repository: TodoRepository;
}

export const TodoContext = createContext<TodoContextInterface>({
  repository: {} as TodoRepository
});


export default TodoContext;
```

Add context around `<App/>`. And set the repository to use on `TodoContext.Provider`
```ts
// index.tsx
ReactDOM.render(
  <React.StrictMode>
    <TodoContext.Provider value={{ repository: new TodoRepositoryElf() }}>
      <App/>
    </TodoContext.Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
```


##React side

###Todo
Todo component will contain all components of app, and communicate with repository via the context `TodoContext` created before.

```ts
// containers/Todo.tsx
const Todo = () => {
  // get repository from context
  const { repository } = useContext(TodoContext);
   
  // Call repo when visibility filter has changed
  const handleVisibility = ((filter: VisibilityFilterProps['filter']) => {
    repository.updateFilter(filter);
  });

  // Call repo when new todo is added 
  const handleAddTodo = ((text: string) => {
    repository.addTodo(text);
  });

  return <div className="container mx-auto p-8 flex flex-col grow h-full">
    <div className="flex flex-col grow">
      <h1 className="text-4xl font-semibold mt-8 mb-8">
        All tasks
      </h1>
      <h2 className="font-semibold uppercase text-xl tracking-wide text-slate-400 mt-8 mb-4">
        Filters
      </h2>
      <VisibilityFilter onChange={handleVisibility}/>
      <TodoList/>
    </div>
    <div>
      <AddTodo onAdd={handleAddTodo}/>
    </div>
  </div>;
};

export default Todo;
```

###TodoList
This component list all todo items. And manage state of todo by complete/active/remove `TodoItem`.

With `useObservable` when `todos$` is updated on repository the value `todos` will be automatically updated.

```ts 
// containers/TodoList.tsx
const TodoList = () => {
  const { repository } = useContext(TodoContext);
  const [todos] = useObservable(repository.todos$);

   // Call repo to mark item completed
  const handlerComplete = (id: string) => {
    repository.markAsComplete(id);
  };
   // Call repo to mark item active
  const handleActive = (id: string) => {
    repository.markAsActive(id);
  };
   // Call repo to remove item
  const handleRemove = (id: string) => {
    repository.removeTodo(id);
  };

  return (
    <div>
      <h2 className="font-semibold uppercase text-xl tracking-wide text-slate-400 mt-8 mb-4">Todo List</h2>
      {todos.map((todo) => (
        <TodoItem todo={todo} onCompleted={handlerComplete} onRemove={handleRemove}
                  onActive={handleActive}
                  key={todo.id}/>
      ))}
    </div>
  );
};

export default TodoList;
```

###VisibilityFilter
"Dumb" component just raised event when `filter` value change. 
```ts 
//components/VisibilityFilter.tsx
const VisibilityFilter = ({ onChange }: { onChange: (filter: VisibilityFilterProps['filter']) => void }) => {
  return (
    <div className="flex gap-x-2 justify-center">
      <ButtonFilter onClick={onChange} id="all">
        All
      </ButtonFilter>
      <ButtonFilter onClick={onChange} id="active">
        Active
      </ButtonFilter>
      <ButtonFilter onClick={onChange} id="completed">
        Completed
      </ButtonFilter>
    </div>
  );
};

export default VisibilityFilter;
```

###AddTodo
Simple form with input and button who raised event `onAdd` with input value when the "add" button is clicked
```ts
// components/AddTodo.tsx
const AddTodo = ({ onAdd }: { onAdd: (name: string) => void }) => {
  const inputRef = useRef<HTMLInputElement>(null);

  const submitHandler = (e: React.FormEvent) => {
    e.preventDefault();
    const todoValue = inputRef.current?.value as string;
    inputRef.current!.value = '';
    if (todoValue) {
      onAdd(todoValue);
    }
  };

  return (
    <div className="">
      <form className="flex mt-4" onSubmit={submitHandler}>
        <input className="shadow appearance-none border rounded w-full py-2 px-3 mr-4 text-slate-900"
               placeholder="Add Todo"
               ref={inputRef}/>
        <button
          className="flex-no-shrink p-2 border-2 rounded-full border-green-500 bg-green-500 hover:text-white hover:bg-green-600 fill-white hover:fill-green-300"
          type="submit">
          <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
               width="24" height="24"
               viewBox="0 0 24 24">
            <path fill-rule="evenodd"
                  d="M 11 2 L 11 11 L 2 11 L 2 13 L 11 13 L 11 22 L 13 22 L 13 13 L 22 13 L 22 11 L 13 11 L 13 2 Z"></path>
          </svg>
        </button>
      </form>
    </div>
  );
};

export default AddTodo;
``` 

### The Single Source Of Truth
Now we have plugged all UI event with the repository, but the repository do nothing at this time, so we are going to do this.

### List
First we are to setup the `todos$`. The list of items will be all entities filtered by the props `filter`. When `all` is applied `all` todos will be listed, `completed` only the todos with `completed:true` will be listed , and `active` only the `completed:false`.

First we get the value of filter, on the repo
```ts 
// repository/todo.repository.ts
export class TodoRepositoryElf implements TodoRepository {
  private todosStore = new Store({ name: 'todos', state, config });
  filter$ = this.todosStore.pipe(select(({ filter }) => filter));
 //....
}
```
Now we have the filter value , we are set the `todos$` observable.
```ts
// repository/todo.repository.ts
//....
  todos$: Observable<Todo[]> = this.filter$.pipe(switchMap((filter) => {
    return this.todosStore.pipe(selectAllApply({
      filterEntity({ completed }): boolean {
        if (filter === 'all') return true;
        return filter === 'completed' ? completed : !completed;
      }
    }));
  }));
//....
```

See in action by implementing `addTodo`. To add entities simply use `addEntities` https://ngneat.github.io/elf/docs/features/entities/entities#addentities
```ts
// repository/todo.repository.ts
  addTodo(text: Todo['name']) {
    this.todosStore.update(addEntities({
      name: text,
      id: Date.now().toString(),
      completed: false
    }));
  }
```
> Try it !
![Add first todo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6400zamjyjcztjt3w88.png)


**Good now implements all methods**
```ts
  markAsComplete(id: string) {
    this.todosStore.update(updateEntities(id, {
      completed: true
    }));
  }

  markAsActive(id: string) {
    this.todosStore.update(updateEntities(id, {
      completed: false
    }));
  }

  removeTodo(id: string): void {
    this.todosStore.update(deleteEntities(id));
  }

  updateFilter(type: VisibilityFilterProps['filter']): void {
    this.todosStore.update((state) => ({
      ...state,
      filter: type,
    }));
  }
```

> And `voilà`. 


##Conclusion
Using Elf it's really easy and simple. You even can use the `todo.repository.ts` in your `Angular` `Vue` `Svelte` app because it's independent to FW.

Source : https://github.com/workfel/react-todo-elf
Demo : https://workfel.github.io/react-todo-elf/