452 Words

Reading time 3 min

Using Material Design Icons in ClojureScript

A lot of stuff in the ClojureScript/React world is documented through implication, as in “In a similar way you can do X, Y, and Z just as you would in a React project without much difficulty”. But I don’t know React, so I run into difficulties and therefore need explicit examples. This post is what I wish I’d found when I originally searched for “material design icons and clojurescript”, documenting how I integrated Material Design Icons into a Helix-based ClojureScript app.This is a note-to-self style blog post. I am building a new website using Postgraphile and ClojureScript. On the ClojureScript side of things, I’m using helix for the moment, seeing how far local data inside of “modern React development” will get me. Today I managed to integrate Material Design Icons, and it took about a half-hour to figure out the semantics, so I want to document what I did.

Installing the necessary libraries

I’m using shadow-cljs for ClojureScript development, so npm is the way to go for integrating node packages. That makes the first step of getting packages into my system identical to the React-specific instructions from the Material Design Icons website:

npm install @mdi/react @mdi/js

The first package gives you an Icon element in React, and the second package makes the design icons available to your React code.

Getting the icons to show up on the page

The MDI website example for React is a good starting point, but I had to change a few things to get the icons to show up inside of ClojureScript/Helix. The original incantation is:

import React, { Component } from 'react'
import Icon from '@mdi/react'
import { mdiAccount } from '@mdi/js'

class App extends Component {
  render() {
    return (
      <Icon path={mdiAccount}
        title="User Profile"
        size={1}
        horizontal
        vertical
        rotate={90}
        color="red"
        spin/>
    )
  }
}

At first I tried using Icon directly, but that failed as Icon is not a function. Following various hints in the Helix docs, the key insight is that “Icon” is to use the $ macro. To quote the docs,

The $ macro takes a component type (string, keyword, or symbol referring to a Component), optionally some props, and any children, and returns a React Element with that same information, like React.createElement.

The correct usage is ($ Icon {:path mdiAccount ...} ). Note that there are no quotes around Icon. To generate a camera image icon placeholder, you can do something like:

(ns myapp.views
  (:require [helix.core :refer [defnc $]]
            [helix.dom :as d]
            ["react-dom" :as rdom]
            ["@mdi/react" :refer (Icon) ]
            ["@mdi/js" :refer (mdiCameraImage mdiAccount)]
            ))
(defn picture-or-placeholder [picture alt]
  (d/div {:class "media-left"}
         (d/figure {:class "image is-48x48"}
                   (if (nil? picture)
                     ($ Icon {:path mdiCameraImage
                              :title "Placeholder image"}
                        )
                     (d/img {:src (:source picture)
                             :alt alt}
                            )))))