1

Here is the problem.

I have simple todo store:

import { makeAutoObservable } from "mobx";
import { Todo } from "./../types";

class Todos {
todoList: Todo[] = [
    { id: 0, description: "Погулять с собакой", completed: false },
    { id: 1, description: "Полить цветы", completed: false },
    { id: 2, description: "Покормить кота", completed: false },
    { id: 3, description: "Помыть посуду", completed: true },
];

// Input: Add Task
taskInput: string = "";

// Filter: query
query: string = "";

// Filter: showOnlyCompletedTasks
showOnlyCompleted: boolean = false;

constructor() {
    makeAutoObservable(this);
}

setShowOnlyCompletedState(value: boolean) {
    this.showOnlyCompleted = value;
}

changeCompletionState(id: number) {
    const task = this.todoList.find((todo) => todo.id === id);
    if (task) task.completed = !task.completed;
}

addTask(text: string) {
    if (text !== "") {
        const newTodo: Todo = {
            id: Number(new Date()),
            description: text,
            completed: false,
        };
        this.todoList.push(newTodo);
    }
}

taskChangeInput(value: string) {
    this.taskInput = value;
}

queryChangeInput(value: string) {
    this.query = value;
}
}

export default new Todos();

In the app I have some tasks, which I can make completed or not-completed (by clicking on it) and also I do have some filters to filter my todo_list. Here is the code:

  • of posts:
import { Todo } from "../types";
import { useMemo } from "react";

function useFilterByQuery (list: Todo[], query: string):Todo[] {
    const filteredList = useMemo(()=>{
        if (!query) return list
        return list.filter(todo => todo.description.toLowerCase().includes(query.toLowerCase()))
    }, [list, query])
    return filteredList
}

export function useFilterByAllFilters (list:Todo[], query: string, showOnlyCompleted: boolean):Todo[] {
    const filteredByQuery = useFilterByQuery(list, query)

    const filteredList = useMemo(()=>{
        if(!showOnlyCompleted) return filteredByQuery
        return filteredByQuery.filter(todo => todo.completed)
    }, [filteredByQuery, showOnlyCompleted])

    return filteredList
}

So the description of the problem is so: when I choose show me only-Completed-tasks (setting showOnlyCompleted to true), I get expected list of tasks which are all 'completed'. But, when I change the state of 'todo' right now, the shown list isn't changing (uncompleted task doesn't filter immediatly), it's changing only after I set showOnlyCompleted to false and back to true.

I assume it's not happening, because I don't 'update' the todoList for MobX, which I provide (using function useFilterByAllFilters) by props in My TodoList component. In my opinion the problem is with the useMemo or with something in Mobx. Please, help me out.

luckyme-
  • 13
  • 2

1 Answers1

0

Yes, it's because of useMemo. useFilterByQuery only has [list, query] deps, but when you change some todo object to be (un)completed you don't really change the whole list, only one object inside of it. And that is why this useMemo does not rerun/recompute, so you still have the old list. Same with useFilterByAllFilters.

What you are doing is not really idiomatic MobX I would say! MobX is designed to work with mutable data and React hooks are designed to work with immutable data, so to make it work with hooks you need to do some workarounds, but it would be easier to just rewrite it like that:

class Todos {
  // ... your other code

  // Add computed getter property to calculate filtered list
  get listByQuery() {
    if (!this.query) return list
    return this.todoList.filter(todo => todo.description.toLowerCase().includes(this.query.toLowerCase()))
  }

  // Another one for all filters
  get listByAllFilters() {
    if(!this.showOnlyCompleted) return this.listByQuery
    return this.listByQuery.filter(todo => todo.completed)
  }
}

And just use this two computed properties in your React components, that's it! No need for hooks. And these properties are cached/optimized, i.e. will only run when some of their observables change.

More info about computeds

Danila
  • 15,606
  • 2
  • 35
  • 67
  • May I ask you one more question about 'controlled' inputs with MobX. You see, how I realized it in my code. But what is the best practice to make it? – luckyme- Jul 24 '22 at 14:03
  • What you have is fine, I guess! If you have huge list or something like that, which is slow to rerender, then maybe you want to have 2 queries, first which actually binds to the input, and debounced second one to use it for filters, that way filters won't recompute on every keystroke – Danila Jul 24 '22 at 15:15