🗡️
ConanJs
  • What is ConanJs?
  • Why ConanJs...
    • ... if coming from Redux
    • ... if using vanilla React
    • ... if learning React
    • ... if not using React
  • How to install/use ConanJs
  • About us / Github / Contact us
  • Demos
    • Demo Gallery
    • Conan State Demos
      • Hello World
      • Todos
        • Todos - Basic
        • Todos - Async
        • Todos - Optimistic
      • Github issues viewer
    • Conan Flow Demos
      • Authentication
  • CONAN DATA
    • General Concepts
    • Conan State
      • Actions & Reducers
        • Reducers
        • Actions
      • Creating State
      • Observing State
        • Live rendering
        • Connecting
      • Composing State
      • Scaling State
      • Orchestrating State
      • Life cycle
        • Async handling
        • Introspection
      • Testing state
    • Conan Flow
      • Creating Flows
      • Serialising Flows
      • Observing Flows
  • CONAN Runtime
  • Dependency Injection
    • General Concepts
    • Creating the Context
    • Using the Context
  • ASAPs
  • Logging
  • API
    • Main Classes
      • Conan
        • StateDef
      • ConanState
      • ConanFlow
        • UserFlowDef
        • UserStatusDef
        • Status
    • Conan State Classes
      • Thread
      • ConnectedState
      • MonitorInfo
      • MetaInfo
    • Dependency Injection
      • DiContextFactory
    • ASAPS
      • Asaps
      • Asap
Powered by GitBook
On this page
  • Introduction
  • Adding the state
  • Implement optimistic updates
  • Adding cancellation
  • Getting the code
  • Code sandbox

Was this helpful?

  1. Demos
  2. Conan State Demos
  3. Todos

Todos - Optimistic

What if we wanted to optimistically add/toggle our todos before the server API call returns?

Introduction

ConanJs allows you to add optimistic updates to your state. In order to achieve this, it provides State monitor and asyncMerge. As you will see, the monitor also allows us to cancel ongoing async calls, and update our local state accordingly.

Adding the state

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

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:

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:

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:

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:

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

Code sandbox

PreviousTodos - AsyncNextGithub issues viewer

Last updated 4 years ago

Was this helpful?

conan-js-examples/todo-list-optimist at master · conan-js/conan-js-examplesGitHub
These TODOs are very optimistic!
Logo