Skip to main content Accessibility Feedback

Creating web app components with vanilla JavaScript

I’m getting ready to launch my latest pocket guide, “Vanilla JS Web Apps.”

This week, we’ve looked at how to render elements, add state to an object (and what “state” even is), and how to automatically re-render elements when state changes—all without using a framework or library.

Today, we’re going to talk about the final piece of the puzzle: how to programmatically turn any template into a component.

Quick heads up: if you haven’t yet, you should definitely read the rest of the articles in this series or some of this won’t make sense.

A component() method

We’re going to use a component() method to handle all off the stuff we did manually in the previous articles.

Let me show you the whole thing, then we’ll talk through it.

var component = function (template, props, elem) {

	// Add properties to our template
	Object.defineProperties(template, {

		// Set the element to render into
		elem: {
			value: elem,
			writable: true
		},

		// Add state
		state: {
			value: props,
			writable: true
		},

		// Add the `setState()` method
		setState: {
			value: function (props) {

				// Shallow merge new properties into state object
				for (var key in props) {
					if (props.hasOwnProperty(key)) {
						template.state[key] = props[key];
					}
				}

				// Render the element
				render(template, template.elem);

				// Return the elem for use elsewhere
				return template.elem;

			}
		}

	});

	// Return the template so you can assign it to a variable if desired
	return template;

};

There are two ways to use our component() method. In each case, we’ll pass in the props argument to access our template’s state.

Example 1: Pass in an existing template

To use it, you can pass in an existing template and assign your initial state.

var todoList = function (props) {

    // Setup our template
    var template = '';

    // Loop through the todos
    for (var i = 0; i < props.todos.length; i++) {
        var todo = props.todos[i];

        // Check if it's completed
        var checked = todo.completed ? 'checked' : '';

        // Create the todo item
        template +=
            '<label>' +
                '<input type="checkbox" value="' + todo.item + '" ' + checked + '>' +
                todo.item +
            '</label>';
    }

    // Return completed template
    return template;

};

component(todoList, {
    todos: [
        {
            item: 'Eat',
            completed: false
        },
        {
            item: 'Take a nap',
            completed: true
        },
        {
            item: 'Eat again',
            completed: false
        }
    ]
}, document.querySelector('#todo-list'));

Our todoList() template is now a component with state, and calling todoList.setState() will cause the associated element to re-render.

Example 2: Create the template with the component() method

You can alternatively set up a template for the first time with the component() method.

var todoList = component(function (props) {

    // Setup our template
    var template = '';

    // Loop through the todos
    for (var i = 0; i < props.todos.length; i++) {
        var todo = props.todos[i];

        // Check if it's completed
        var checked = todo.completed ? 'checked' : '';

        // Create the todo item
        template +=
            '<label>' +
                '<input type="checkbox" value="' + todo.item + '" ' + checked + '>' +
                todo.item +
            '</label>';
    }

    // Return completed template
    return template;

}, {
    todos: [
        {
            item: 'Eat',
            completed: false
        },
        {
            item: 'Take a nap',
            completed: true
        },
        {
            item: 'Eat again',
            completed: false
        }
    ]
}, document.querySelector('#todo-list'));

And just like the previous example, calling todoList.setState() will cause a re-render.

How this works

The Object.defineProperties() method is used to assign properties to a JavaScript object (and in JavaScript, confusingly, everything is an object, not just actual objects like {}).

What makes it nicer than just doing object.property = 'something' is that you can also set whether or not people should be able to modify it, if it should show up in loops, and so on.

With our component() method, we’re defining the template’s elem and state properties. We’re also assigning a function to setState(), and automatically passing in in the template’s state property.