React Native: Building cross platform components

It’s surprisingly simple to use skills gained from using React on the web and apply them to React Native. In fact it’s not just simple, but satisfying, thanks to the ease of following React Native’s simple React ‘Getting Started’ guide in order to see the basic "Hello World" application, all with only a limited knowledge of native app development.

React Native allows you to take the same code and run it on both iOS and Android bringing your project to life on multiple platforms. It does a remarkable job of making sure this is possible the vast majority of the time. However, iOS and Android are different. React Native never claims to be a "write once, run anywhere" solution. Instead, it adopts a different approach; "learn once, write anywhere". This is not just true but its biggest strength, borne out by just how easy it is to get started with limited knowledge of app development, as long as you have an understanding of React.

But React Native isn’t a miracle worker. You won’t be able to take code from the web and run it as a native app. Equally, in certain cases you can’t (and wouldn’t want to) take code written for iOS and run it on Android, or vice versa. The fact remains that sometimes you need to run different code for different platforms.

The Problem

The difficulty with using the same codebase for two different platforms is in creating a user interface that looks and feels native to whichever platform it is running on as Google and Apple have each adopted their own design guidelines, Material Design and
iOS Human Interface Guidlines. Both are very different from each other and whilst you can try to design an interface that is neutral to both, there will inevitably come a point where it needs to be different on each platform.

The Solution

Approaching React Native development by considering how what you’re building will work on either platform is essential. By avoiding a narrow focus on one or the other and instead considering them both you’ll save invaluable time and energy trying to port an entire app to a different platform. You can achieve this using a component based user interface, creating components that work cross platform and that can be re-used multiple times throughout the app. Anyone already familiar with React will already know that it lends itself really well to this.

A very basic example of this are any touchable areas within an app. These are handled very differently. In most recent versions of Android it is handled by it’s native ripple effect, whereas on iOS there is a more subtle effect. So how can you create a native touch to each platform by using the same component in our code? React makes this easy. So let’s build a simple component that would tackle this problem…

This isn’t going to be a tutorial on getting set up with React Native, so for that I’m going to refer you to their documentation on how to get started.

1. Import the Dependencies

Firstly, you need to import the dependencies that you’re going to be using. Instead of importing the entire React libraries you can use object destructuring in ES6 to only import the components that you need.

import React, { Component } from 'react';
import {
  View,
  Text,
  Platform,
  TouchableOpacity,
  TouchableNativeFeedback
} from 'react-native';

2. Create our Component

If you are unfamilliar with using ES6 classes and are used to using the old syntax React.createClass make sure to check out the differences in the react docs here. You are able to use the Platform API provided by react native to render different code depending on which platform the app is running.

class Touch extends Component {
  render() {
    if (Platform.OS === 'ios') {
      // code for ios
    }

    // code for android
  }
}

3. Adding the Touchable Components

You can use the Touchable Opacity componenet for a more subtle iOS like touch feedback and to create the Android ripple effect you can use the TouchableNativeComponent. Both are provided by react native.

class Touch extends Component {
  render() {
    if (Platform.OS === 'ios') {
      return (
        <TouchableOpacity onPress={() => {}}>
          <View>
            <Text>Touch</Text>
          </View>
        </TouchableOpacity>
      )
    }

    return (
      <TouchableNativeFeedback
        onPress={() => {}}
        background={TouchableNativeFeedback.Ripple(#fff, false)}>
          <View>
            <Text>Touch</Text>
          </View>
      </TouchableNativeFeedback>
    )
  }
}

4. Making it dynamic

If you want to re-use this component throughout our app you need to make it dynamic. In react we can use props to achieve this.

class Touch extends Component {
  render() {
    if (Platform.OS === 'ios') {
      return ( 
        <TouchableOpacity onPress={this.props.onPress}>
          <View style={this.props.style}>
            {this.props.children}
          </View>
        </TouchableOpacity>
      )
    }

    return (
      <TouchableNativeFeedback
        onPress={this.props.onPress}
        background={TouchableNativeFeedback.Ripple(this.props.rippleColor, this.props.bordlessRipple)}>
          <View>
            {this.props.children}
          </View>
      </TouchableNativeFeedback>
    )
  }
}

You can now use this component anywhere throughout your app and pass down different props each time. Notice how we are using this.props.children. This will take any children of our component and render them wherever it is used allowing us to have dynamic content.

<Touch
  onPress={() => action()}
  style={styles.btn}
  rippleColor="#fff"
  borderlessRipple={false}>
    <Text>Touch</Text>
</Touch>

5. Tidying Up

There are a few things you could do to tidy up things up:

Firstly you could use object destructing to prevent repetition of this.props. Secondly, you could use set default props using Component.defaultProps so that the components props don’t always have to be specified. As the component needs specific prop types to work it is best practice enforce these using Component.propTypes.

class Touch extends Component {
  render() {
    const { onPress, style, children, rippleColor, borderlessRipple } = this.props;

    if (Platform.OS === 'ios') {
      return (
        <TouchableOpacity onPress={onPress}>
          <View style={style}>
            {children}
          </View>
        </TouchableOpacity>
      )
    }

    return (
      <TouchableNativeFeedback
        onPress={onPress}
        background={TouchableNativeFeedback.Ripple(rippleColor, borderlessRipple)}>
          <View>
            {children}
          </View>
      </TouchableNativeFeedback>
    )
  }
}

Touch.propTypes = { 
  onPress: React.PropTypes.func,
  style: React.PropTypes.object,
  rippleColor: React.PropTypes.string,
  borderlessRipple: React.PropTypes.bool
}

Touch.defaultProps = {
  onPress: () => {},
  style: styles.btn,
  rippleColor: '#fff',
  borderlessRipple: false
}

Now we only have to pass down props if we wish to change the default values meaning we have a component that is easy to re-use and read.

<Touch onPress={() => action()}>
  <Text>Touch</Text>
</Touch>

And there you have it. In just a few lines of code we have a component that utilises the native touch of its platform thanks to the power of React Native. There are many different use cases for this approach and while this is just a basic example it can be utilised to create great native experiences on both platforms.

Want to know a little more about us?

We are a friendly bunch and love talking all things WordPress. Whether you’re using one of our open source projects, want to discuss a blog article or hire us for your next project, we would love to hear from you.

Contact us