How to remove an element from a Mobx observable array, without causing the entire consuming component to rerender?

So let’s say i have a todoStore. It has an action that deletes a todo by id. Note that i tried both filter and splice:

export default class TodosStore {
    constructor() {
    makeAutoObservable(this)  
    }

    todos = [
        {
            id: 1,
            name: "name1",
            completed: true
        },
        {
            id: 15,
            name: "name2",
            completed: true
        },
        {
            id: 14,
            name: "name3",
            completed: true
        }
    ]

    removeTodo(id) {
        // this.todos = this.todos.filter(todo=>todo.id != id)

        for (let todo of this.todos) {
            if (todo.id == id) {
                const indexOf = this.todos.indexOf(todo)
                this.todos.splice(indexOf, 1)
            }
        }
    }    
};

The consuming Todos component(Note that i’m wrapping the Todo with observer):

import { combinedStores } from "."    
const ObservableTodo = observer(Todo);

export default observer(() => {
    const { todosStore } = combinedStores       

    return (
        <div >
          {todosStore.todos.map(todo=>{
              return(
                <ObservableTodo onDelete={()=>{todosStore.removeTodo(todo.id)}} onNameChange={(value)=>{todosStore.editTodoName(todo.id,value)}} key={todo.id} todo={todo}></ObservableTodo>
               )
            })}
        </div>
    )
})

The simple Todo component:

export default ({todo,onNameChange,onDelete}) => {
    return (
        <div style={{padding:'10px',margin:'10px'}}>
            <p>ID: {todo.id}</p>
            <input onChange={(e)=>{onNameChange(e.target.value)}}  value={todo.name}></input>
            <p>Completed: {todo.completed ? 'true' : 'false'} <button onClick={onDelete} className="btn btn-danger">Delete</button></p>            
        </div>
    )
}

Even though i’m clearly mutating(as opposed to constructing a new array) the todos array within the store, Todos component rerenders(i see it via console.logs), and so does every remaining Todo component.

Is there any way around it? Is there anything wrong with my setup perhaps? I’m using latest Mobx(6) and mobx-react.

Answer

Todos component is supposed to rerender because it depends on todos array content (because it map‘s over it). So when you change todos content by adding or removing some todo – Todos component will rerender because it needs to render new content, new list of todos.

Each single Todo rerenders because you have not wrapped it with observer. It is a good practice to wrap every component which uses some observable state, and Todo is clearly the one that does.