Categories
Tutorials

Managing Internal State in Mithril Components

This tutorial will highlight why you would want to use closures instead of raw objects to manage the iternal state of your mithril component.

We will start by building a basic traffic light component.

<!DOCTYPE HTML>
<html>
<head>
	<title>Mithril: Internal State</title>
	<link rel="stylesheet" href="style.scss" />
</head>
<body>
	<div id="app"></div>
	<script type="module" src="index.js"></script>
</body>
</html>
import m from 'mithril';

const trafficLight = () => {

	return {
		
		view: () => []
	}
}

document.getElementById("app") && m.mount( document.getElementById('app'), {
	view: vnode => m(trafficLight)
});

Right now, the component is not doing much of anything, except that a lexical scope is created when the trafficLight component is mounted.

Now we will define a state inside the parent scope of the trafficLight component. Nothing complex, just a basic “state” variable with the value of “go”:

const trafficLight = () => {
	
	let state = 'go';
...

We will needs lights and switches, of course. Instead of defining these individually, let’s use an array of objects to define the lights and switches:

const trafficLight = () => {
	
	let state = 'go';
	
	const lights = [{
		className: '.red',
		action: 'stop',
	},{
		className: '.yellow',
		action: 'caution',
	},{
		className: '.green',
		action: 'go',
	}];
	
	const switches = [{
		action: 'stop',
		label: 'Stop',
	},{
		action: 'caution',
		label: 'Caution',
	},{
		action: 'go',
		label: 'Go',
	}]
...

Finally, let’s render the lights and corresponding switches:

	return {
		
		view: () => [
			
			lights.map( ({action, className}) => m(className)),
			
			switches.map( ({action, label}) => m('button', label))
		]
	}

Right now all we see are 3 grayed out lights. We will have to pass the initial state to our lights. In this instance, we will use an “active” class to render the color of each active light. This is done by matching the state with the defined action of each light. If the state matches the action of the light, an “active” class is added to the light, turning it on.

			lights.map( ({action, className}) => m(className,{
				class: [
					action === state ? 'active' : '',
				].join(' ').trim()
			})),

The active green light now reflects the initial state defined in the parent scope. What we want now is to change the state when each switch is pressed. This is easily accomplished by assigning the desired action to the state in the button’s onclick event:

			switches.map( ({action, label}) => m('button',{
				onclick: () => {
					state = action;
				}
			}, label))
import m from 'mithril';

const trafficLight = () => {
	
	let state = 'go';
	
	const lights = [{
		className: '.red',
		action: 'stop',
	},{
		className: '.yellow',
		action: 'caution',
	},{
		className: '.green',
		action: 'go',
	}];
	
	const switches = [{
		action: 'stop',
		label: 'Stop',
	},{
		action: 'caution',
		label: 'Caution',
	},{
		action: 'go',
		label: 'Go',
	}]
	
	return {
		
		view: () => [
			
			lights.map( ({action, className}) => m(className,{
				class: [
					action === state ? 'active' : '',
				].join(' ').trim()
			})),
			
			switches.map( ({action, label}) => m('button',{
				onclick: () => {
					state = action;
				}
			}, label))
		]
	}
}

document.getElementById("app") && m.mount( document.getElementById('app'), {
	view: vnode => m(trafficLight)
});

Now we have a fully functional traffic light. Note that the state is only altered in the inner scope while the initial state in the parent scope is preserved.

What if we need to pass an updated state to functions defined in the parent scope? We’ll tackle that in part 2 of this tutorial.

Tutorial link: https://github.com/wlcdesigns/mithril-manage-internal-state

Leave a Reply

Your email address will not be published. Required fields are marked *