# Todos - Optimistic

### Introduction

ConanJs allows you to add *optimistic* updates to your state. In order to achieve this, it provides State [monitor](broken://pages/-M9sjuF--Cq3HxHBZoZM) and [asyncMerge](broken://pages/-M9sjuF--Cq3HxHBZoZM).  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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.conanjs.io/tutorials/conan-state-demos/todos/todos-optimistic.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
