My thoughts on: Mithril
Tags: webdev, javascript, mithril, tutorial
Published: 2021-04-25 18:10:25

Gone were the days when we would add a small `<script></script>` into our HTML to include a library into our projects. Now we have bundlers and transpilers (like [Webpack](https://webpack.js.org/) and [Babel](https://babeljs.io/)), and now we have other frontend build tools like [Snowpack](https://www.snowpack.dev/). Finally, we have the ability to import modules into our browsers (except IE).

[JQuery](https://jquery.com/) was the UI library of choice, but now we have [Angular](https://angular.io/), [React](https://reactjs.org/) and [Vue](https://vuejs.org/). There are a few others I have had the privilege to work, namely [Lit-Element](https://lit-element.polymer-project.org/guide) [AureliaJS](https://aurelia.io/), and [MithrilJS](https://mithril.js.org/).

---

## Now, moving on...

MithrilJS isn't a UI Library alone (like React or Vue). It is an actual framework that **"provides routing and XHR utilities out of the box"** as it says on their website.

It is fast, super light and leverages on the power of VirtualDOM. The community is vibrant and energetic too with quick responses to questions etc. The API is relatively small and you would be surprised at what you can get done within a small amount of time with this framework. If you love the idea of working with plain JavaScript objects without having to worry about learning some form of new template syntax, look no further. There may be the odd occasion where you (or your team) may need to deliver a small-medium sized project in a short space of time without the additional bloat (and learning curve) that other frameworks may bring. Mithril (in my opinion) would be a viable option in that respect.

---

### Installation
Installation is pretty simple. Just copy and paste this link into your web app.
```html
<script src="https://unpkg.com/mithril/mithril.js"></script>
```

...or via NPM, run the following command in your terminal. ```
$ npm install mithril --save
```

If you would like to integrate it to your **[TypeScript](https://www.typescriptlang.org/)** project, you can do so by simply running the following command in your terminal.

```node
$ npm install mithril --save
$ npm install @types/mithril --save-dev
```

If you would like to use Mithril with **JSX**, follow the instructions [here](https://mithril.js.org/jsx.html#setup).

---

### Mithril Components
There are three ways to create a component in Mithril.
```javascript
// Function
function Component() {
    let type = 'Functional component...'

    return {
        view() {
            return m('div', `This is the ${type}.`);
        }
    }
}
```

```javascript
// Class
class Component {
    oninit() {
        this.data = {
            type: 'class component'
        }
    }

    view() {
        return m('div', `This is the ${this.data.type}.`);
    }
}
```

```javascript
// Object literal
const Component = {
  data: {
    type: 'object literal component'
  },
  view() {
    return m('div', `This is the ${this.data.type}.`);
  }
};
```


---

### Mithril Function
The Mithril function is very similar (in its syntax) to React without JSX. You can take a look [here](https://reactjs.org/docs/react-without-jsx.html). 

```javascript
// A Mithril function can take 2-3 arguments
// m(elementName, textContent)
// m(elementName, attributes, textContent)
import m from 'mithril';

// 2 arguments
const element = m('div', 'Hello world');

// 3 arguments
const attributes = {
  onclick: () => console.log('You have clicked on me!')
};
const elementWithAttributes = m('button', attributes, 'Click me!');


/**
 * To display your component...
 *
 * This is your equivalent in:
 * - React <DummyComponent name={'World'} /> 
 * - Angular <app-dummy-component [name]="'World'"></app-dummy-component>
 * - VueJS <dummy-component .name="'World'" />
 */
const DummyComponent = {
  view(vnode) {
    return m('div', `Hello ${vnode.attrs.name}`);
  }
}
m(DummyComponent, { name: 'world' });

// ...and to nest elements, you would do this
m('div',
  m('p', 'This is a post on MithrilJS.'),
  m('button', 'Click me!'),
);
```

We can see a common thread in the components. The `view` method serves as function that returns your mithril element. You can either return `m()` or your can return an array of `m()` components.

I personally love to separate my components into two types: "Smart" and "Dumb" components. 

```javascript
const Model = {
  getUsers() {
    return m.request({
      method: "GET",
      url: "https://jsonplaceholder.typicode.com/users"
    });
  }
}

class AppComponent {
  oninit() {
    // declare properties upon component initialization
    this.data = {
      selected: null,
      users: []
    };

    // get the users, and assign them
    Model.getUsers().then(
      users => this.data.users = users,
      err => console.error(err)
    );
  }

  /**
   * React equivalent of:
   * <section>
   *  <UserComponent user={this.state.selected} />
   *  <UserListComponent selected={() => this.onUserSelect()} users={this.state.users} />
   * </section>
   * 
   * Angular equivalent of:
   * <section>
   *  <app-user [user]="selected"></app-user-component>
   *  <app-user-list [users]="users" (selected)="onUserSelect()"></app-user-component>
   * </section>
   */
  view() {
    return m('section',
      m(UserComponent, { user: this.data.selected }),
      m(UserListComponent, {
        selected: user => this.onUserSelect(user),
        users: this.data.users
      }),
    );
  }

  // events can go here

  onUserSelect(user) {
    this.data.selected = user;
  }
}


// dumb components
class UserListComponent {
  // extract the selected and users properties from the "attributes" keys
  view({ attrs: { selected, users } }) {
    return users
      ? users.map(user => m('p', { onclick: () => selected(user) }, `${user.name} (${user.email})`))
      : m('div', 'No users available atm.');
  }
}

class UserComponent {
  view({ attrs: { user } }) {
    return user ? m('div', `You have selected ${user.name}`): '';
  }
}
```

---

### Lifecycle Hooks
Mithril has its own set of lifecycle hooks. Here they are:
```javascript
class Component {
  constructor() {
    this.data = {
      name: 'World',
    };
  }

  // "oninit" is run before DOM element is attached 
  oninit(vnode) {}

  // "oncreate" is run after the DOM element is attached
  oncreate(vnode) {}

  // "onbeforeupdate" is run before the DOM element is updated
  onbeforeupdate(newVnode, oldVnode) {}

  // "onupdate" is run when the DOM element is updated whilst attached to the document
  onupdate(vnode) {}

  // "onbeforeremove" is run before the DOM element is detached
  onbeforeremove(vnode) {}

  // "onremove" is when the DOM element has been detached
  onremove(vnode) {}

  view(vnode) {
    return m('div', `Hello ${this.data.name}`);
  }
}

m.mount(document.body, Component);
```

---

### Routing
Now, for the sake of this post, we will stick with the `class` approach to developing our components.

```javascript
// m.route(element, homeUrl, links)
class HomeComponent {
  view() {
    return m('div', `Home component.`);
  }
}

class UserComponent {
  view() {
    return m('div', `User component.`);
  }
}

class ErrorComponent {
  view() {
    return m('div', `There is an error!`);
  }
}

class UserEditComponent {
  view({ attrs }) {
    console.log('"ID" Parameter:', attrs.id);
    return m('div', 'This is the User component to edit.');
  }
}

m.route(document.body, "/", {
  "/": HomeComponent,
  "/users/:id": UserComponent,
  "/users/:id/edit": {
    onmatch: () => {
      // once the URL has been matched
      if (localStorage.getItem('jwt')) {
        return UserEditComponent;
      }

      return ErrorComponent
    },
    render: vnode => {
      // code all you want here before the "vnode" is injected into the component
      const authenticated = randomSource.get('key');
      return [{ ...vnode, authenticated }];
    },
  }
});
```

---

### Sharing data between components

You can easily share data between two components for example:
```javascript
const Store = {
  state: { counter: 0 },
  incrementCounter() {
    this.state.counter++;
  },
}

class DisplayComponent {
  view() {
    return m('div', `You have clicked on the button ${Store.state.counter} times.`);
  }
}

class Component {
  view() {
    return [
      // display the DIV element -> that would contain BUTTON element
      m('div',
        m('button', {
          onclick: () => Store.incrementCounter()
        }, `Counter`)
      ),

      // display the DISPLAY component
      m(DisplayComponent),
    ];
  }
}
```

---

### In conclusion...
The developers of MithrilJS have done a fantastic job in creating a fast and light framework, with a strong API. There are some other things I may not have mentioned at the top, but can be found on the [MithrilJS](https://mithril.js.org/) website. 

It is:
* Light (9.5kb)
* Pretty rapid
* Simple to integrate in an existing project
* Backed by a strong vibrant community
* Helps improve your JavaScript since there is no "template language"
* Fun to use

If this has helped you, please feel free to leave a comment below. If you have questions, or would disagree, I'm open to discuss.