javascriptcyclejsxstream-js

Cyclejs input not resetting after redraw


I tried to create a simple todo app with Cyclejs/xstream. The app works fine. Only thing I not able to understand is after adding each todo the input should clear, which is not happening.

todo.js

import {
    div, span, p, input, ul, li, button, body
}
from '@cycle/dom'
import xs from 'xstream'
import Utils from './utils'

export function Todo(sources) {
    const sinks = {
        DOM: view(model(intent(sources)))
    }
    return sinks
}

function intent(sources) {
    return {
        addTodo$: sources.DOM.select('input[type=text]').events('keydown').filter((ev) => {
            return ev.which == 13 && ev.target.value.trim().length > 0;
        }).map((ev) => {
            return ev.target.value;
        }),
        deleteTodo$: sources.DOM.select('.delete').events('click').map((ev) => {
            return Number(ev.target.getAttribute('data-id'));
        }).filter((id) => {
            return !isNaN(id);
        }),
        completeTodo$: sources.DOM.select('.complete').events('click').map((ev) => {
            return Number(ev.target.getAttribute('data-id'));
        }).filter((id) => {
            return !isNaN(id);
        })
    };
}

function model(action$) {
    let deleteTodo$ = action$.deleteTodo$.map((id) => {
        return (holder) => {
            let index = Utils.findIndex(holder.currentTodos, 'id', id);
            if (index > -1) holder.currentTodos.splice(index, 1);
            return {
                currentTodos: holder.currentTodos,
                value: ''
            };
        };
    });
    let completeTodo$ = action$.completeTodo$.map((id) => {
        return (holder) => {
            let index = Utils.findIndex(holder.currentTodos, 'id', id);
            if (index > -1) holder.currentTodos[index].completed = !holder.currentTodos[index].completed;
            return {
                currentTodos: holder.currentTodos,
                value: ''
            };
        };
    });
    let addTodo$ = action$.addTodo$.map((item) => {
        return (holder) => {
            let todo = {
                value: item,
                id: holder.currentTodos.length + 1,
                completed: false
            };
            holder.currentTodos.push(todo);
            return {
                currentTodos: holder.currentTodos,
                value: ''
            };
        };
    });
    return xs.merge(deleteTodo$, addTodo$, completeTodo$)
        .fold((holder, modifier) => {
            return modifier(holder);
        }, {
            currentTodos: [],
            value: ''
        });
}

function view(state$) {
    return state$.map((state) => {
        console.log(state);
        return div({
            attrs: {
                class: 'todo'
            }
        }, [
            input({
                props: {
                    type: 'text',
                    value: state.value
                }
            }),
            ul({
                attrs: {
                    class: 'text'
                }
            }, state.currentTodos.map((todo) => {
                return li({
                    attrs: {
                        class: `${todo.completed ? 'completed' : 'open'}`
                    }
                }, [
                    span(todo.value),
                    button({
                        attrs: {
                            class: 'delete',
                            'data-id': todo.id

                        }
                    }, 'XXXXX'),
                    button({
                        attrs: {
                            class: 'complete',
                            'data-id': todo.id

                        }
                    }, 'CCCCC')
                ]);
            }))
        ]);
    });
}

utils.js

var Utils = {
    filter: function(array, fn) {
        var results = [];
        var item;
        for (var i = 0, len = array.length; i < len; i++) {
            item = array[i];
            if (fn(item)) results.push(item);
        }
        return results;
    },
    findItem: function(array, fn) {
        for (var i = 0, len = array.length; i < len; i++) {
            var item = array[i];
            if (fn(item)) return item;
        }
        return null;
    },
    findIndex: function(array, prop, value) {
        var pointerId = -1;
        var index = -1;
        var top = array.length;
        var bottom = 0;
        for (var i = array.length - 1; i >= 0; i--) {
            index = bottom + (top - bottom >> 1);
            pointerId = array[index][prop];
            if (pointerId === value) {
                return index;
            } else if (pointerId < value) {
                bottom = index;
            } else if (pointerId > value) {
                top = index;
            }
        }
        return -1;
    }
};

export default Utils;

Solution

  • You need to put hook inside input element. It will work as expected. If you want you can send another default value (in this case it is empty string).

    input({
         props: {
              type: 'text'
         },
         hook: {
              update: (o, n) => n.elm.value = ''
         }
     }),
    

    @cycle/dom driver should be > 11.0.0 which works with Snabbdom. But if you use earlier version you need:

    var Hook = function(){
        this.arguments=arguments;
    }
    Hook.prototype.hook = function(node) {
        node.value=this.arguments[0];
    }
    
    
    input({ attributes: {type: 'text'},
          'my-hook':new Hook('')
    })