Search code examples
reactjsmobxmobx-reactmobx-react-lite

Updating Todo list via mobx with react and typescript


Hi guys i was just trying out mobx today and made a Todo List with it, the problem is the list isn't updating here after the item is removed from the list even though i had made observer and observable. You can also directly check my code here at CodeSanbox .It's a basic todo app

Store.js

import { observable, action, computed, reaction } from "mobx"
import { createContext } from "react"
import { v4 as uuidv4 } from 'uuid';
export interface Todo {
  id?: string;
  title: string;
  completed: boolean;
}

class TodoStore {
  constructor() {
    reaction(() => this.todos, _ => console.log(this.todos.length))
  }

  @observable todos: Todo[] = [
    { id: uuidv4(), title: "Item #1", completed: false },
    { id: uuidv4(), title: "Item #2", completed: false },
    { id: uuidv4(), title: "Item #3", completed: false },
    { id: uuidv4(), title: "Item #4", completed: false },
    { id: uuidv4(), title: "Item #5", completed: true },
    { id: uuidv4(), title: "Item #6", completed: false },
  ]

  @action addTodo = (todo: Todo) => {
    this.todos.push({ ...todo, id: uuidv4() })
  }

  @action toggleTodo = (id: string) => {
    this.todos = this.todos.map(todo => {
      if (todo.id === id) {
        return {
          ...todo,
          completed: !todo.completed,
        }
      }
      return todo
    })
  }

  @action removeTodo = (id: string) => {
      console.log(id)
    this.todos = this.todos.filter(todo => todo.id !== id)
    console.log(this.todos)
  }

  @computed get info() {
    return {
      total: this.todos.length,
      completed: this.todos.filter(todo => todo.completed).length,
      notCompleted: this.todos.filter(todo => !todo.completed).length,
    }
  }
}

export default createContext(new TodoStore())

TodoList.js

import React, { useContext } from "react";
import TodoStore from "../stores/TodoStore";
import { observer } from "mobx-react-lite";

const TodoList = () => {
  const todoStore = useContext(TodoStore);
  const { todos, toggleTodo, removeTodo } = todoStore;
  return (
    <>
      <div className="row">
        <table className="table table-hover">
          <thead className="thead-light">
            <tr>
              <th>Title</th>
              <th>Completed?</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {todos.map(todo => (
              <tr key={todo.id}>
                <td>{todo.title}</td>
                <td>{todo.completed ? "✅" : ""}</td>
                <td>
                  <button
                    className="btn btn-sm btn-info"
                    onClick={_ => toggleTodo(todo.id!)}
                  >
                    Toggle
                  </button>
                  <button
                    className="btn btn-sm btn-danger"
                    onClick={_ => removeTodo(todo.id!)}
                  >
                    Remove
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </>
  );
};

export default observer(TodoList);

So as i click remove button, the action "removeTodo" gets fired up the todo is removed from the list i can check that on console but it istn't updating in the UI part


Solution

  • With Mobx v6 simply setting decorators is not enough anymore. You also need to call makeObservable or makeAutoObservable in the class constructor.

      import { observable, action, computed, reaction,makeAutoObservable } from "mobx"
    
      constructor() {
        makeAutoObservable(this)
        reaction(() => this.todos, _ => console.log(this.todos.length))
      }
    

    Sandbox demo