# Todos - Optimistic

### Introduction

ConanJs allows you to add *optimistic* updates to your state. In order to achieve this, it provides State [monitor](https://docs.conanjs.io/tutorials/conan-state-demos/todos/broken-reference) and [asyncMerge](https://docs.conanjs.io/tutorials/conan-state-demos/todos/broken-reference).  As you will see, the monitor also allows us to cancel ongoing async calls, and update our local state accordingly.

### Adding the state&#x20;

We need to slightly modify our Todo definition to make it optimistically updatable. It just needs to hold a status per todo item:

```typescript
export enum OptimisticStatus {
    SETTLED = 'SETTLED',
    IN_PROCESS = 'IN_PROCESS',
}

export interface OptimisticData<T> {
    data: T;
    status: OptimisticStatus;
    cancelCb: ICallback;
}

export interface OptimisticTodoListData {
    todos: OptimisticData<ToDo>[];
    appliedFilter: VisibilityFilters;
}
```

�Now we will enrich our app with a new optimistically updatable state. For that in our Conan's DI definition file, we have to add:

```typescript
export interface OptimisticApp extends App {
    optimisticTodoListState: ConanState<OptimisticTodoListData, TodoListActions>
}

export let diContext = DiContextFactory.createContext<OptimisticApp, InternalDependencies>({
        todoListState: TodoListAsyncStateFactory.create,
        optimisticTodoListState: OptimisticTodoListData$
    }, {
        todoListService: TodoListServiceImpl
    },
);
```

### �Implement optimistic updates

The key part here is how the optimisticTodoListState is created, as it is in fact a merged state of the state's monitor info and the state's data:

```typescript
export function OptimisticTodoListData$(todoListState: TodoListState): ConanState<OptimisticTodoListData, TodoListActions> {
    return todoListState.asyncMerge<OptimisticTodoListData>(
        {
            appliedFilter: VisibilityFilters.SHOW_ALL,
            todos: []
        },
        (monitorInfo, data, current) => {
            if (monitorInfo.currentAction == null) {
                return current;
            }

            let asyncTodo: ToDo = monitorInfo.currentAction.payload [0];
            if (monitorInfo.status === MonitorStatus.ASYNC_CANCELLED) {
                return ({
                    appliedFilter: current.appliedFilter,
                    todos: current.todos.filter(it => it.data.id !== asyncTodo.id)
                })
            }

            if (monitorInfo.status !== MonitorStatus.ASYNC_START) {
                return current;
            }

            return monitorInfo.currentAction.name === 'addTodo' ? {
                appliedFilter: current.appliedFilter,
                todos: [...current.todos, {
                    status: OptimisticStatus.IN_PROCESS,
                    data: asyncTodo,
                    cancelCb: () => {
                        if (monitorInfo.currentAction == null) {
                            throw new Error(`unexpected error`);
                        }

                        monitorInfo.currentAction.asap.cancel()
                    }
                }]
            } : monitorInfo.currentAction.name === 'toggleTodo' ? {
                    appliedFilter: current.appliedFilter,
                    todos: current.todos.map(todo => todo.data.id !== asyncTodo.id ? todo : {
                        status: OptimisticStatus.IN_PROCESS,
                        data: {
                            ...asyncTodo
                        },
                        cancelCb: () => {
                            if (monitorInfo.currentAction == null) {
                                throw new Error(`unexpected error`);
                            }

                            monitorInfo.currentAction.asap.cancel()
                        }
                    })
                } :
                current
        },
        (data, monitorInfo, optimisticData) => ({
            appliedFilter: data.appliedFilter,
            todos: Lists.mergeCombine(
                data.todos,
                optimisticData.todos,
                (todo, optimisticTodo) => todo.id === optimisticTodo.data.id,
                (todo) => ({
                    status: OptimisticStatus.SETTLED,
                    data: todo,
                    cancelCb: () => {
                    }
                })
            )
        })
    )
}
```

### �Adding cancellation

As the app can now access a merged state that also contains the monitor actions, it can invoke the cancel action. For example, we have slightly modified the Todo react component, to include a **loading**... and a **cancel button**:

```typescript
interface TodoProps {
    toggleCb: ICallback;
    completed: boolean;
    text: string;
    status: OptimisticStatus,
    cancelCb: ICallback,
    id: string
}

export class OptimisticTodo extends React.Component<TodoProps> {
    render() {
        return (
            <li
                key={this.props.id}
                style={{
                    textDecoration: this.props.completed ? 'line-through' : 'none'
                }}
            >
                {this.props.text} <button onClick={this.props.toggleCb}>toggle</button>{this.props.status === OptimisticStatus.IN_PROCESS &&
                    <>'  ...LOADING!...' <button onClick={this.props.cancelCb}>cancel</button></>
                }
            </li>
        );
    }
}
```

### Getting the code�

Please feel free to visit our examples at:

{% embed url="<https://github.com/conan-js/conan-js-examples/tree/master/todo-list-optimist>" %}

or the code sandbox below, for more details on this ConanJs feature.

### Code sandbox

{% embed url="<https://codesandbox.io/s/chzft>" %}
These TODOs are very optimistic!
{% endembed %}
