Add Custom Font to React Native using Expo

Today we are going to learn how to apply the custom font to our react native app. In this post, we are using expo to run our very basic app to display the native font and custom font.

Expo has some built-in modules to deal with various functionality. We are going to use the Font module to load the custom font. You can import this module like the below example.

import * as Font from 'expo-font';

Let’s just open the App.js file and add 2 lines with the React Native Text component.

<View style={styles.container}>
    <Text style={styles.heading}>Custom Roboto Font</Text>
    <Text style={{fontSize: 40}}>Default Font</Text>
</View>

The first line will display with the custom font and the second line with display the default font. We are using the Roboto Medium to apply to these texts and will store the downloaded font to the /assets/fonts directory.

We can use the lifecycle method componentDidMount() to load the font.

componentDidMount() {
    Font.loadAsync({
        'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf')
    });
}

Feel free to copy and paste the below code to test your custom font.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import * as Font from 'expo-font';

export default class App extends React.Component {

    componentDidMount() {
        await Font.loadAsync({
            'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf')
        });
    }

    render() {

        return (
            <View style={styles.container}>
                <Text style={styles.heading}>Custom Roboto Font</Text>
                <Text style={{fontSize: 40}}>Default Font</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center'
    },
    heading: {
        fontFamily: 'roboto-medium', 
        fontSize: 40
    }
});

The code is correct but when you run your app, then you get the following error.

fontFamily “roboto-medium” is not a system font and has not been loaded through Font.loadAsync

And this is because the app view renders before the font loads. Let’s add some waiting to avoid this error. We can display the ActivityIndicator before the font completely loads.

The next step is to take a state with property assetsLoaded and the default false value. And change the state to true once the font loading.

state = {
    assetsLoaded: false,
};

async componentDidMount() {
    await Font.loadAsync({
        'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf')
    });

    this.setState({ assetsLoaded: true });
}

Now move to the render section and display the view depending on assetsLoaded state. We will display the ActivityIndicator and StatusBar component until the font finished its loading process.

render() {

    const {assetsLoaded} = this.state;

    if( assetsLoaded ) {
        return (
            <View style={styles.container}>
                <Text style={styles.heading}>Custom Roboto Font</Text>
                <Text style={{fontSize: 40}}>Default Font</Text>
            </View>
        );
    }
    else {
        return (
            <View style={styles.container}>
                <ActivityIndicator />
                <StatusBar barStyle="default" />
            </View>
        );
    }
}

Please find the complete codes to run and test.

import React from 'react';
import { StyleSheet, Text, View, StatusBar, ActivityIndicator } from 'react-native';
import * as Font from 'expo-font';

export default class App extends React.Component {

    state = {
        assetsLoaded: false,
    };

    async componentDidMount() {
        await Font.loadAsync({
            'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf')
        });
    
        this.setState({ assetsLoaded: true });
    }

    render() {

        const {assetsLoaded} = this.state;

        if( assetsLoaded ) {
            return (
                <View style={styles.container}>
                    <Text style={styles.heading}>Custom Roboto Font</Text>
                    <Text style={{fontSize: 40}}>Default Font</Text>
                </View>
            );
        }
        else {
            return (
                <View style={styles.container}>
                    <ActivityIndicator />
                    <StatusBar barStyle="default" />
                </View>
            );
        }
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center'
    },
    heading: {
        fontFamily: 'roboto-medium', 
        fontSize: 40
    }
});

Custom Font load with React Navigation

If you like to load your custom font with react-navigation then you can achieve it with a slightly different approach.

import React from 'react';
import { StyleSheet, View, StatusBar, ActivityIndicator } from 'react-native';
import * as Font from 'expo-font';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

import HomeScreen from './src/screens/HomeScreen'
import ProfileScreen from './src/screens/ProfileScreen';

const MainNavigator = createStackNavigator({
    Home: {screen: HomeScreen},
    Profile: {screen: ProfileScreen},
});
  
const AppContainer = createAppContainer(MainNavigator);

export default class App extends React.Component {

    state = {
        assetsLoaded: false,
    };

    async componentDidMount() {
        await Font.loadAsync({
            'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf')
        });
    
        this.setState({ assetsLoaded: true });
    }

    render() {

        const {assetsLoaded} = this.state;

        if( assetsLoaded ) {
            return (
                <AppContainer
                    ref={nav => {
                        this.navigator = nav;
                    }}
                />
            );
        }
        else {
            return (
                <View style={styles.container}>
                    <ActivityIndicator />
                    <StatusBar barStyle="default" />
                </View>
            );
        }
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center'
    },
});