Todo List
This tutorial is to replicate the function of the famous TodoMVC project.  Using @uteamjs/react, the total number of lines is less than 100.

Click here to run the application.

Import

#
import { utCreateElement, utform, utReducer } from '@uteamjs/react'

Fields Initialization

#
First we initialize the reducer with a unique identifierreact-example-todo/todolist’.
const reducer = utReducer('react-example-todo/todolist', {
    init: {
        isEdit: true,
        fields: {
            todo: {
                label: 'Todo List',
                hint: '... type item then press [enter]'
            },
            item: {}
        },
        i: 0, view: 'all', todos: {}
    },
    actions: {...}
})
Properties The properties of the init object are defined as follows:
fields.todo
For inputting new todo items. The default field type is text, so it is not specified here.  For details, refer to field properties definition.
fields.item
Used for editing on existing items.
view
Storing the current view state ‘All’, ‘Active’ or ‘Completed’

todos
Object for storing all todo items in key value pairs where:
key - is an automatically generated label n1, n2, n3 …. etc. and
value  - item object {
   item: content of the input item.
   tp: [completed|active] - current state of item.
}
i
Counter used to generate the todos’ key label.

Actions

#
actions: {
    add: (_, todo) => _.todos['n' + ++_.i] = { item: todo, tp: 'active' },

    delete: (_, key) => delete _.todos[key],

    complete: (_, key) => _.todos[key].tp =
        _.todos[key].tp === 'completed' ? 'active' : 'completed',

    setview: (_, tp) => tp === 'clear completed' ?
        each(_.todos,(v, k)=> v.tp==='completed' ? delete _.todos[k] :null):    
        _.view = tp,

    edit: (_, key) => {
        if (!_.fields.item.key) {   // Prevent multiple edit
            _.todos[key].isEdit = true
            _.fields.item.value = _.todos[key].item
            _.fields.item.key = key}},

    done: (_) => {
        const key = _.fields.item.key
        _.todos[key].isEdit = false
        _.todos[key].item = _.fields.item.value
        delete _.fields.item.key},
}
Logics of the actions are explained in the following table:
add
New key is being generated using the i counter.  A todo item is assigned to the todos object with the new key.
delete
Delete the item todos with the passed in key.
complete
Toggle the tp of item todos with the passed in key.
setview
Set the view with the given tp.
edit
Set the isEdit property of the item with the key to true.  Assign the current item value to fields.item
done
Reset the isEdit property,  Assign the value from field.item back to the current item.

Layout

#

Event Handler

#
onKeyPress = ({ id, char, value }) => {
    if (char === 'Enter') {
        if (id === 'todo')
            this.props.call('add', value)

        else if (id === 'item')
            this.props.call('done')
    }
}
onKeyPress event handler will be triggered upon the user pressing any key on every input field.
There are three properties passing in:
id
ID of the current input field
char
The key pressed
value
Input string value of current field
So whenever an Enter key is pressed, if the field id is todo then call the add action else if the field is item then call the done action.  For more details, please refer to event handler.

Stylesheet

#

The todo.sass stylesheet has introduced a line-through style to display complete items as crossed out.
import '../css/todo.sass'
    ...
    .completed
        > label > span
            text-decoration-line: line-through

Render

#

Input Field

#

Field todo for inputting new items.
<Field id='todo' labelPosition='top' />

Item List

#

For each object in the list, if the isEdit property is true then render <Field id=’item’/> for item modification, otherwise render the check item using <Form.Check />  component.  

There is a local variable cnt for calculating the number of filtered items.  If the user toggles the checkbox, the complete action will be called.  

If double clicked on the item, edit action will be called
{map(_.todos, (obj, key) => _.view === 'all' || _.view === obj.tp ?
                   obj.isEdit ?
                       <Field key={'item-' + ++cnt} id='item' className='edit' labelPosition='none' /> :
                       <Form.Check key={'item-' + ++cnt} type='checkbox' className={obj.tp}
                           label={this.todoItem(obj, key)}
                           checked={obj.tp === 'completed'}
                           onChange={() => call('complete', key)}
                       />
                   : null)}

Footer

#

The current view filter is shown with the number of filtered items vs total items.  Links are rendered to toggle the view filter and clear all completed items.

<div className='footer'>
    <div className='count'>{_.view}: {cnt} /
        {Object.keys(_.todos).length}</div>
    {['All', 'Active', 'Completed', 'Clear Completed'].map(tp =>
        <a key={tp} onClick={() => call('setview', tp.toLowerCase())}>
            {tp}
        </a>)}
</div>