DEV Community

Georgy Glezer
Georgy Glezer

Posted on

React Hooks + MobX TodoList

Today we are going to write a simple to-do list with React Hooks and MobX.

A little introduction about these libraries to people who is not familiar with, if you already know it, you can pass this section.

React

A JavaScript library for building user interfaces, introduced by Facebook in 2013 and is used to display UI in web applications, unlike Angular, Vue, etc.. React is not a framework and provides only tools to display UI components.

React Hooks

For the people who already know React, Hooks is the enhancement of functional components, for more detailed information you can read inside the link.

MobX

MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP). The philosophy behind MobX is very simple(from the docs).

Now that we know what we are going to use, we can start working on the project.

Let's open a new react project, There is a big starter project called Create React App supported by the community and have all the required features to start a new react app. (Configuring a new react project may take time, so it’s nice to have this boilerplate). We are going to use TypeScript also for this :)

Open a terminal and write the next commands:

  • install create-react-app with typescript
  • install types needed for typescript(under devDependencies as we don’t use them in runtime)
  • install mobx and it’s connection react
npx create-react-app my-app --template typescript

npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest

npm i mobx mobx-react-lite

MobX uses decorates so we will need to add to our tsconfig the next line "experimentalDecorators": true .

Let’s set up our mobx stores and connect it to our react project, our first steps will be:

  • Create TodoList and TodoItem
  • Create Context and wrap our app with its provider to share the mobx stores
  • Create a way to get the store from the context
export class TodoList {
    @observable.shallow list: TodoItem[] = [];

    constructor(todos: string[]) {
        todos.forEach(this.addTodo);
    }

    @action
    addTodo = (text: string) => {
        this.list.push(new TodoItem(text));
    }

    @action
    removeTodo = (todo: TodoItem) => {
        this.list.splice(this.list.indexOf(todo), 1);
    };

    @computed
    get finishedTodos(): TodoItem[] {
        return this.list.filter(todo => todo.isDone);
    }

    @computed
    get openTodos(): TodoItem[] {
        return this.list.filter(todo => !todo.isDone);
    }
}

We create TodoList class that holds @observable array of TodoItem , notice we assign it an initial value and make the observable shallow so it won’t wrap by itself the values inside the array. We make it receive an array of strings to initialize our list and add 2 simple functions of add and remove, we use arrow functions on actions because we rely on this and we don’t want to lose the context while doing actions todos.forEach(this.addTodo) , on the getter functions, we don’t arrow functions because we only use it as get.

export default class TodoItem {
    id = Date.now();

    @observable text: string = '';
    @observable isDone: boolean = false;

    constructor(text: string) {
        this.text = text;
    }

    @action
    toggleIsDone = () => {
        this.isDone = !this.isDone
    }

    @action
    updateText = (text: string) => {
        this.text = text;
    }
}

We create simple TodoItem class and give it 2 observable properties of text and isDone and 2 functions to toggle it status and update the text.

Now we are going to use React Context, it allows us to share data between all of the react components inside the provider it gives us, we will create the context using createContext from react, and pass it default value an empty object, and assign it the type of our store TodoList (usually on big project it can be root store).

import { createContext } from 'react';
import {TodoList} from "../stores/todo-list";

export const StoreContext = createContext<TodoList>({} as TodoList);
export const StoreProvider = StoreContext.Provider;

We go to our index.tsx file, create our todo list store and wrap or <App /> component with the provider we got from our context and pass it the todoList as the value.

const todoList = new TodoList([
    'Should Starting Writing in React',
    'Should Learn MobX',
    'Should Watch Once Piece :)'
]);


ReactDOM.render(
    <StoreProvider value={todoList}>
        <App/>
    </StoreProvider>
    , document.getElementById('root'));

Lastly, we add a function to help us get the stores inside the react functions, using useContext react provides us, we pass it the Context we created earlier and receive the value of we provided(todoList).

export const useStore = (): TodoList => useContext(StoreContext);

Now we can start writing our react hooks components 🎆, We’re going to create 3 components:

  • TodoList
  • TodoItem
  • TodoNew

We will start with TodoList :

export const TodoList = () => {
    const todoList = useStore();

    return useObserver(() => (
        <div className="todo-list">
            <div className="open-todos">
                <span>Open Todos</span>
                {todoList.openTodos.map(todo => <TodoItem key={`${todo.id}-${todo.text}`} todo={todo}/>)}
            </div>
            <div className="finished-todos">
                <span>Finished Todos</span>
                {todoList.finishedTodos.map(todo => <TodoItem key={`${todo.id}-${todo.text}`} todo={todo}/>)}
            </div>
        </div>
    ));
};

We use our helper function useStore to retrieve the todoList, then we simply return 2 lists of finished and unfinished todos from the computed values of the todo list store.

it’s important to use here useObserver from mobx-react-lite to wrap the return value to let mobx know to track the observable values inside, without that if you will try to update the list from outside it won’t work( you can read more about it here https://mobx-react.js.org/observe-how).

export const TodoItem = ({todo}: Props) => {
    const todoList = useStore();
    const [newText, setText] = useState('');
    const [isEditing, setEdit] = useState(false);

    const saveText = () => {
      todo.updateText(newText);
      setEdit(false);
      setText('');
    };

    return (
        <div className="todo-item">
            {
                isEditing ?
                    <div>
                        <input type="text" onKeyDown={onEnterPress(saveText)} onChange={(e) => setText(e.target.value)}/>
                        <button onClick={saveText}>save</button>
                    </div>
                    :
                    <div>
                        <span>{todo.text}</span>
                        <input type="checkbox" onChange={todo.toggleIsDone} defaultChecked={todo.isDone}></input>
                        <button onClick={() => setEdit(true)}>edit</button>
                        <button onClick={() => todoList.removeTodo(todo)}>X</button>
                    </div>
            }
        </div>
    )
};

We create TodoItem which receives the mobx class model of a todo in the props. We give the component basic abilities of edit, remove and update its text. The important thing here is to see how we let the todo update the text itself and no need to the update the whole list for that( i added onEnterPress helper function to make the check for enter key easier), here we didn’t use useObserver because the todo is already observed inside TodoList .

The TodoNew component you can see in the Github repository, I won’t add it here because it’s the same concepts and code.

Github Repo: https://github.com/stolenng/react-hooks-mobx

To sum up, this simple guide shows how we connect react and hooks with mobx using mobx-react-lite , I think this combination of react function components(hooks) with mobx produces awesome code, really easy and straightforward. You can use this as a starter project with mobx and adjust it as you like.

If anybody has questions or just wants to talk about this topic, more than welcome to PM me :)

* Adding links here to libraries mentioned in the article, for some reason mobile links on medium don’t work :/

Oldest comments (0)