DEV Community

James Allardice for orangejellyfish

Posted on • Originally published at orangejellyfish.com

Custom icon fonts with React Native

When working with icons in React Native apps we're spoilt for choice with
a wide range of free and open-source icon sets such as FontAwesome,
Material and Ionicons. To make things even easier, the
wonderful react-native-vector-icons project bundles all of those icon
sets plus more into one single package. But sometimes free and open-source icon
sets just don't cut it and you're left wondering how to achieve something that
has the same developer experience for a custom icon set. Fortunately,
react-native-vector-icons and a bunch of other projects have us covered here
too.

Setting up react-native-vector-icons

If you're using Expo and have not ejected to ExpoKit then there's
nothing to do here. Expo bundles a wrapper around react-native-vector-icons in
the @expo/icons package.

Otherwise, the installation of the react-native-vector-icons package is as you
would expect for a React Native app. It's published to npm so you can add it to
your project with the CLI or equivalent (we tend to use Yarn when
working with React Native because it plays better with Expo):

$ yarn add react-native-vector-icons
$ react-native link react-native-vector-icons
Enter fullscreen mode Exit fullscreen mode

Generating an icon font

With react-native-vector-icons set up in your project you are ready to work on
the icons themselves. In our experience IcoMoon is the most effective
tool here. IcoMoon is a web application that allows you to import SVG files and
produce font files in various formats from collections of those SVGs, as shown
in the following screenshot:

Creating an icon set in IcoMoon
An example of creating an icon set in IcoMoon

Once all of your icons are imported to the IcoMoon app you can select them and
"Generate" the font file (note that in the screenshot below it shows the number
of selected icons to the left of the highlighted "Generate Font" button):

Generating an icon font in IcoMoon
An example of generating an icon font from an icon set in IcoMoon

There are a few options to configure the resulting font but most of the time the
defaults will suffice. When you're happy download the bundle and unzip it to
find a selection of font files, some examples and a selection.json file. It's
that file plus the *.ttf font file that we need. Copy those files to a
sensible directory within your React Native codebase. We usually go for a top-
level assets directory which contains all of the static assets used by the app
including fonts and images.

Using the custom icon font

It's recommended that you pre-load any fonts that your app is going to use and
your new custom icon font is no exception. In your main app entry point you can
use the Font.loadAsync method. If you have used the Expo CLI to initialise
your project then you probably have something that looks like this already:

import React from 'react';
import { registerRootComponent, AppLoading } from 'expo';
import * as Font from 'expo-font';

class App extends React.Component {
  state = {
    isLoadingComplete: false,
  };

  loadResourcesAsync = async () => Promise.all([
    Font.loadAsync({
      'custom-icons': require('../assets/fonts/custom-icons.ttf'),
    }),
  ]);

  handleLoadingError = (error) => {
    // In this case, you might want to report the error to your error
    // reporting service, for example Sentry
    console.warn(error);
  };

  handleFinishLoading = () => {
    this.setState({ isLoadingComplete: true });
  };

  render() {
    const { isLoadingComplete } = this.state;

    if (!isLoadingComplete) {
      return (
        <AppLoading
          startAsync={this.loadResourcesAsync}
          onError={this.handleLoadingError}
          onFinish={this.handleFinishLoading}
        />
      );
    }

    return (
      <App />
    );
  }
}

registerRootComponent(App);

// Export the App component for unit testing purposes. Expo handles rendering
// via the "registerRootComponent" call above and does not require an export.
export default App;
Enter fullscreen mode Exit fullscreen mode

With this configuration your custom icon font file will be loaded at app start-
up rather than at first usage which would otherwise result in flashes of
unstyled (or missing) content.

Next up you need a normal React component to render icons from your new font.
The react-native-vector-icons package provides some utility methods to make this
process simpler. The following few lines are all that are needed. We usually
place this in a src/components/icon/index.js file:

import { createIconSetFromIcoMoon } from 'react-native-vector-icons';
import icoMoonConfig from '../../../assets/fonts/selection.json';

// We use the IcoMoon app (https://icomoon.io) to generate a custom font made up
// of SVG icons. The actual font file is loaded up-front in src/index.js.
export default createIconSetFromIcoMoon(icoMoonConfig, 'custom-icons');
Enter fullscreen mode Exit fullscreen mode

The key points to note here are the import of the selection.json file from the
bundle downloaded from IcoMoon and the name of the font, custom-icons, as
defined in the Font.loadAsync call in the main app entry point.

The createIconSetFromIcoMoon function could be thought of as a factory that
returns a React component. You can now import that component from your other
components to render icons. The following example imagines a simple "button"
component in src/components/button/index.js:

import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import Icon from '../icons';

const Button = () => (
  <TouchableOpacity>
    <Icon name="settings" />
    <Text>Settings</Text>
  </TouchableOpacity>
);

export default Button;
Enter fullscreen mode Exit fullscreen mode

The new Icon component supports all of the props that the open-source icon
sets bundled with react-native-vector-icons support. This means you can apply
custom styles, such as sizes and colours, from React Native stylesheets.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.