Back to tutorials
  • 1Before We Start
  • 2Dependencies
  • 3Components
  • 4Configuring State
  • 5Rendering Data
  • 6Event Listeners
  • 7Updating State
  • 8Adding Todos
Metal.js HomeMetal.js
  • Documentation
Learn to create a simple Todo App with Metal.js and Soy

Adding Todos

You almost have a fully functioning Todo App! The only missing feature is the ability to add new todos to the list. This is where the TodoForm component comes into play.

First, go ahead and add a couple of event listeners: one for the form submission (onsubmit) and one for when the input value changes (onkeyup):

// TodoForm.js

class TodoForm extends Component {
    handleSubmit(event) {
        // Prevent default browser functionality
        event.preventDefault();
    }

    handleChange(event) {
    }
}
{namespace TodoForm}

/**
 * This renders the component's whole content.
 */
{template .render}
    <form class="todo-form" data-onsubmit="handleSubmit">
        <label for="title">
            Todo <input data-onkeyup="handleChange" name="title" />
        </label>
        <button type="submit">Add</button>
    </form>
&#123;/template&#125;

Now you can use the value property from STATE to keep track of the changes made to the input value. This makes the input a controlled input, where the value of the input is controlled by the state of the component. In other words, when the value of the state changes, it automatically changes the value in the input as well, keeping the two in sync.

// TodoForm.js

class TodoForm extends Component {
    ...

    handleChange(event) {
        this.value = event.target.value;
    }
}
&#123;namespace TodoForm&#125;

/**
 * This renders the component's whole content.
 */
&#123;template .render&#125;
    {@param? value: string}

    <form class="todo-form" data-onsubmit="handleSubmit">
        <label for="title">
            Todo <input data-onkeyup="handleChange" name="title" value="{$value}" />
        </label>
        <button type="submit">Add</button>
    </form>
&#123;/template&#125;

The value of this.value will now match the value in the input.

You can also emit a custom event when the form is submitted, similarly to what TodoItem does to notify the parent component that something happened:

// TodoForm.js

class TodoForm extends Component {
    ...

    handleSubmit(event) {
        event.preventDefault();

        if (this.value) {
            this.emit('todoAdd', {
                title: this.value
            });

            // Clears the input value
            this.value = '';
        }
    }
}

Then in the TodoApp component, you can listen to this event and add a new todo:

// TodoApp.js

class TodoApp extends Component {
    addTodo(title) {
        // Creates a new array with all of the elements
        // from the previous array, with the newly added item
        this.todos = [...this.todos, {
            done: false,
            title
        }];
    }

    handleTodoAdd(event) {
        this.addTodo(event.title);
    }

    ...
}
&#123;namespace TodoApp&#125;

/**
 * This renders the component's whole content.
 */
&#123;template .render&#125;
    {@param? handleTodoAdd: ?}
    {@param? handleTodoClick: ?}
    {@param? todos: ?}

    <div class="todo-app">
        ...

        {call TodoForm.render}
            {param events: ['todoAdd' : $handleTodoAdd] /}
        {/call}
    </div>
&#123;/template&#125;

Now when you add some text to the input and submit the form, a new todo is automatically added to the list.

That's it! You've successfully created a simple Todo App with Metal.js. Now that you are comfortable with the basics, check out the guides for more advanced documentation.

Finished GIF