🗡️
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
  • Initial steps
  • Adding ConanJs to the project
  • Implementing the Todos app
  • Domain model
  • Creating the Todos state
  • Connecting the state
  • Adding a Todo
  • Toggling a Todo
  • Filtering Todos
  • Code sandbox

Was this helpful?

  1. Demos
  2. Conan State Demos
  3. Todos

Todos - Basic

A step by step guide to create a Todos application with ConanJs

PreviousTodosNextTodos - Async

Last updated 4 years ago

Was this helpful?

In ConanJs we wanted to be original, and we have decided to start showing how to build a list of Todos in our first tutorial. We will then go a bit further than other frameworks do, and will show how we can add Asynchronous calls and optimistic updates.

The code for this example is available at GitHub:

and you can also check this code sandbox at the bottom of this page.

If you are coming from Redux, you might want to see how this compares to the TODO from Redux, if that is the case, have a look at this article:

Initial steps

Adding ConanJs to the project

In the project root, just run:

npm install conan-js-core

Implementing the Todos app

Domain model

Our examples are in typescript, if you use javascript, you can just ignore the type definitions and it will all work just fine on your end.

The following interfaces are to define with Typescript the domain for the application.

Our data structure for this example will be very straightforward, simply having a Todo, TodoList and status entities. We will also model the visibility filters as:

export enum ToDoStatus {
    PENDING = 'PENDING',
    COMPLETED = 'COMPLETED'
}

export interface ToDo {
    id: string;
    description: string;
    status: ToDoStatus;
}

export enum VisibilityFilters {
    SHOW_ALL = 'SHOW_ALL',
    SHOW_COMPLETED = 'SHOW_COMPLETED',
    SHOW_ACTIVE = 'SHOW_ACTIVE'
}

export interface TodoListData {
    todos: ToDo[];
    appliedFilter: VisibilityFilters;
}

Creating the Todos state

To create the state we will use Conan.state as we will need to pass our custom reducers later on. The data part of our state will be represented by the TodoListData, which holds an array of Todos and a current applied filter. We are initially just passing one reducer to add a Todo to the current array of todos.

export const todoListSyncState$ = Conan.state({
    name: 'todos-sync',
    initialData: {
        appliedFilter: VisibilityFilters.SHOW_ALL,
        todos: []
    },
    reducers: ({
        $addTodo: (todo) => ({
            todos: [...getState().todos, todo],
            appliedFilter: getState().appliedFilter
        })
    }),
});
export type TodoListState = ConanState<TodoListData>;


export const todoListSyncState$: TodoListState  = Conan.state<TodoListData>({
    name: 'todos-sync',
    initialData: {
        appliedFilter: VisibilityFilters.SHOW_ALL,
        todos: []
    },
    reducers: getState => ({
        $addTodo: (todo: ToDo): TodoListData => ({
            todos: [...getState().todos, todo],
            appliedFilter: getState().appliedFilter
        })
    }),
});

Let's add our newly defined state to Conan Dependency Injection, so that is accesible from all the app.

export let diContext = DiContextFactory.createContext({
    todoListState: todoListSyncState$,
});
export let diContext: App = DiContextFactory.createContext<App>({
        todoListState: todoListSyncState$,
    }
);

Connecting the state

To use our new reducer and add a Todo, we just need to connect our presentation components with Conan state. For a guide on the different ways of connecting the state please visit Using the State

For this example we will use Conan HOC StateConnect:

<StateConnect from={diContext.todoListState} into={TodoListRenderer} 
    fallbackValue={{
            todos: [],
            appliedFilter: VisibilityFilters.SHOW_ALL
}}/>
<StateConnect<TodoListData, TodoListActions>
    from={diContext.todoListState}
    into={TodoListRenderer}
    fallbackValue={{
        todos: [],
        appliedFilter: VisibilityFilters.SHOW_ALL
    }}
/>

As we have added our state to Conan DI, we can just use it with diContext.todoListState

Adding a Todo

To add a Todo, TodoListRenderer can now make use of our state data and actions. It can now include the AddTodo component and pass the action addTodo created by ConanJs, from the reducer we created before:

<AddTodo onClick={actions.addTodo}/>

Toggling a Todo

To toggle a Todo we will add a new reducer to the Conan state, and pass it in the reducers input parameter:

$toggleTodo: (toggledTodo) => ({
        todos: getState().todos.map(todo => todo.id !== toggledTodo.id ? todo : {
            ...todo,
            status: (todo.status === ToDoStatus.PENDING ? ToDoStatus.COMPLETED : ToDoStatus.PENDING)
        }),
        appliedFilter: getState().appliedFilter
}),
$toggleTodo: (toggledTodo: ToDo): TodoListData => ({
        todos: getState().todos.map(todo =>
            todo.id !== toggledTodo.id ? todo : {
                ...todo,
                status: (todo.status === ToDoStatus.PENDING ? ToDoStatus.COMPLETED : ToDoStatus.PENDING)
            },
        ),
        appliedFilter: getState().appliedFilter
})

TodoListRenderer can now render the list of Todos and pass the action to toggle them:

<ul>
    (data.todos).map(todo => 
    <Todo key={todo.id} onClick={() => actions.toggleTodo(todo)} 
        text={todo.description} completed={todo.status === ToDoStatus.COMPLETED}/>)}
</ul>
{filterToDos(data.todos, data.appliedFilter).map(todo =>
    <Todo
        key={todo.id}
        onClick={() => actions.toggleTodo(todo)}
        text={todo.description}
        completed={todo.status === ToDoStatus.COMPLETED}
    />
)}

Filtering Todos

Let's add the last reducer to the Conan state in order to filter Todos:

$filter: (filter) => ({
    todos: getState().todos,
    appliedFilter: filter
})
$filter: (filter: VisibilityFilters): TodoListData => ({
    todos: getState().todos,
    appliedFilter: filter
})

TodoListRenderer can now pass the filter action to the Footer component:

<FooterRenderer appliedFilter={data.appliedFilter} filterUpdater={actions.filter}/>

We can use the current filter now to only show the correct Todos:

<ul>
    {filterToDos(data.todos, data.appliedFilter).map(todo => <Todo key={todo.id} onClick={() => actions.toggleTodo(todo)} text={todo.description} completed={todo.status === ToDoStatus.COMPLETED}/>)}
</ul>

export function filterToDos(todos, filter) {
    switch (filter) {
        case VisibilityFilters.SHOW_ALL:
            return todos;
        case VisibilityFilters.SHOW_COMPLETED:
            return todos.filter(t => t.status === ToDoStatus.COMPLETED);
        case VisibilityFilters.SHOW_ACTIVE:
            return todos.filter(t => t.status === ToDoStatus.PENDING);
        default:
            throw new Error('Unknown filter: ' + filter);
    }
}
<ul>
    {filterToDos(data.todos, data.appliedFilter).map(todo =>
        <Todo
            key={todo.id}
            onClick={() => actions.toggleTodo(todo)}
            text={todo.description}
            completed={todo.status === ToDoStatus.COMPLETED}
        />
    )}
</ul>

export function filterToDos(todos: ToDo[], filter: VisibilityFilters): ToDo[] {
    switch (filter) {
        case VisibilityFilters.SHOW_ALL:
            return todos;
        case VisibilityFilters.SHOW_COMPLETED:
            return todos.filter(t => t.status === ToDoStatus.COMPLETED);
        case VisibilityFilters.SHOW_ACTIVE:
            return todos.filter(t => t.status === ToDoStatus.PENDING);
        default:
            throw new Error('Unknown filter: ' + filter);
    }
}

Code sandbox

All the files needed for this example are available in this codesandbox. Please be aware that although we explain the stops in both Javascript and Typescript, our examples are written in Typescript.

conan-js-examples/todo-list at master · conan-js/conan-js-examplesGitHub
Logo
ConanJs vs Redux. Comparing a simple TODO AppMedium
Logo