Skip to content

Using Expo to build a mobile app

A full stack web developer shares learnings from a recent Expo/react-native project.

Expo is an open-source toolkit and platform that allows you to build a mobile application from a single codebase and release it to Android, iOS and the web simultaneously. We have been working with Expo recently, so we can share some observations and tips to give you a head start on your next mobile development project.

Expo offers a strong development workflow

If you are a developer who likes seamless workflows, you will really appreciate the ability to start Expo and test the code with a browser when developing for mobile. It allows you to get an app up and running and start iterating on the code within seconds. You can view the content with a web browser, and you don't need to install the Android or iOS development environments initially.

Some quirks

The browser automatically refreshes the page when an update is made to the code, which means you lose the screen you were working on. Because of the refresh, you may want to use React Router to ensure that you do not lose the screen you are working on. However, the Expo app replaces the new components seamlessly without any need to navigate back to the screen you were developing.

Some have found the Expo app to be buggy, forcing shutdown and restart on both the running app and the Expo app.

Every Expo component uses Flexbox

Surprisingly, all components use Flexbox by default. Flexbox makes it much easier to organise UI components than the old methods of positioning components with absolute, float and other CSS attributes.

One of the main differences with web is that the default flex-direction in mobile is column rather than row. With most people holding their phone in portrait mode most of the time, this makes sense.

React-native styles are not CSS

Although most style attributes are familiar, there are small differences between react-native and regular CSS when it comes to styling. The two main differences I noticed were:

  1. React-native stylesheets do not cascade.
  2. There are no pseudo elements, selectors or CSS animations.

Testing must be done on your target platform

To be certain that your Expo code will work on your target platform, you need to test it on that platform. Even if your code works in a browser, it may not start in native. Browsers are more forgiving about what can be accepted. Some CSS and components will behave differently, and some code may not run on mobile.

This may be problematic for a developer whose main testing platform is the browser because you must always do final checks on the Expo app with an actual phone. Irrespective of the platform you are using, you will need to test thoroughly for runtime errors and inconsistencies on ALL target platforms before releasing your app.

Example 1: CSS flex

Your CSS types must be of the correct type. Using the wrong value type in some CSS styles may work fine on web but can throw a runtime error on mobile. This is the case for the flex CSS attribute that requires a number instead of a string.

The flex: '1' CSS will work for web but will fail at runtime for mobile: These errors can be prevented by using Typescript.

Example 2: Sizing images

To display images so that they occupy 100% of the available width, telling react-native to scale the image to 100% of the parent container will not always work. It may also add unwanted padding above and below the image, depending on its ratio.

This is a known issue with a known workaround. One solution involves manually calculating the parent container size and adapting the image to it:

  1. Determine the image width to height ratio from its size. (Note that the method to obtain an image’s size is different on mobile and web).
  2. Obtain the parent component size from the onLayout event.
  3. Set the image size to the parent’s size multiplied by the image’s ratio.
Plain Text
import { StatusBar } from 'expo-status-bar';
import React, { useState, useCallback } from 'react';
import { Image, Platform, StyleSheet, View } from 'react-native';
export default function App() {
  const image = require('./assets/nearform_logo_full.png')
  const [imageSize, setImageSize] = useState({width: 100, height: 100})
  const updateImageSize = useCallback(
    function updateImageSize(event) {
      const containerWidth = event.nativeEvent.layout.width
      if (Platform.OS === 'web') {
        Image.getSize(image, (width, height) =>
            width: containerWidth,
            height: containerWidth * (height / width),
      } else {
        const size = Image.resolveAssetSource(image)
          width: containerWidth,
          height: containerWidth * (size.height / size.width),
  return (
    <View style={styles.root}>
      <View onLayout={updateImageSize}>
        <Image source={image} style={{...styles.image, ...imageSize}}/>
      <StatusBar style="auto" />
const styles = StyleSheet.create({
  root: {
    flex: 1,
    backgroundColor: '#fff',
    justifyContent: 'center',
    alignContent: 'center',
  image: {
    width: 200,
    height: 200,
Plain Text

Sizing an image to 100% of parent width in all devices and all types of layouts requires dynamic sizing.

Expo's limitations are resolvable

Expo offers many benefits, but if you want to use Expo for “standard” applications, you need to be aware of two limitations that will restrict the kind of application you can build:

  1. Expo’s managed workflow does not support native components in the same way that react-native does. Expo’s default ‘Managed’ workflow is akin to create-react-app. This means that, unless a JS module is provided by Expo, you may miss out on Android and/or iOS functionalities. In practice, it generally means that you must accept the lowest common denominator for all three platforms (Web, Android and iOS), as well as the modules already provided by Expo. The solution is to use Expo with the Bare Workflow. In this way, you can avail of the full capabilities of native development.
  2. Expo’s ecosystem is not as rich as React or mobile native. This is also true for react-native with regards to React. Expo is based on react-native, so it also it comes with the limits of react-native. React-native is younger than React with a smaller community. This is completely understandable because the library must support many platforms with little standardisation between them and maintaining expo packages is quite resource intensive.

However, Expo supports forking code for each platform, so its limitations can be mitigated: You can choose to write code that is specific to a platform , such as iOS or web, simply by appending the platform name to a file. For example, you can write a module.ios.js that will be specific to iOS devices. Of course, the same can be done programmatically by detecting the current platform that the code is running on.

Expo is easily deployed as a web app

You can build your Expo project as a web application and host it in the same way you would any react front-end. This will also allow your mobile users to bookmark the app’s webpage. It will look and behave as if it were a mobile app and will not display the frames and buttons that come with their browsers.

Expo offers a quick and easy way to build a mobile application from a single codebase and release it to multiple platforms. It has some quirks, but these can be mitigated with readily available solutions and workarounds.

Insight, imagination and expertly engineered solutions to accelerate and sustain progress.