How to create responsive layouts in React Native

Jakub Zieliński | 10 Oct | 9 min read

Native Apps developers always put a lot of effort into making beautiful apps with a rich UI and which are stable on every supported device. For iOS, this means just a few devices. For Android it could be over a dozen. When you code an app with React Native you can add up these numbers and then multiply it by two, since every device can be rotated. In this short article I’ll try to show you a few tools and tricks which can help you deal with the huge range of devices without going crazy!

1. First of all, FLEXBOX

Components can control layout with a flexbox algorithm. It’s created to keep the proportions and consistency of the layout on different screen sizes.

Flexbox works very similar to CSS on the Web with just a few exceptions which are really easy to learn. When flex prop is a positive number, then components become flexible and will adjust to the screen respective to its flex value. That means that flex equates to flexGrow: [number], flexShrink: 1, flexBasis: 0.

When flex: 0 — it’s sized accordingly to the height and width and is inflexible.
If flex is a negative number it also uses height and width but if there is not enough space it will shrink to its minHeight and minWidth.

There are few main properties provided by flexbox, so let’s get through them!
Flex describes how elements divide space between them. As mentioned above it’s limited to single numbers.

If all elements have flex: 1 they will have the same width.
In other cases they will split the sum of the flex among themselves

Flex direction — we can pick a row or a column (you can also set the reverse) to set your main axis along which your content will be placed. By default flexDirection is set to a column instead of row — which is caused by the nature of the screen on mobile devices.

justifyContent — this prop helps you to position content inside a container along the main axis. You can check few possible placements below:

Align Items — it works the same as justifyContent but align items on the perpendicular axis to the main axis.

Flex prop do a really good job of keeping proportions between elements. Regardless of screen size. FlexDirection and justifyContent keep layout behaviour consistent.

There are many more flexbox props. I touched just a few to show how they can be helpful.

2. Aspect Ratio

Another cool prop is aspect ratio (it’s available only in React Native) which helps keep proportions of your elements under control. If you know only one dimension of your element (width or height), it keeps the second dimension in relation to the one you already know.

3. Screen Dimensions

It is great when your designs are the same for both platforms, and all types of devices (mobile, tablets, ipads). However, sometimes we have to deal with different layouts for specific screen dimensions or device types.

By default React Native doesn’t provide properties which clearly answer which device or what screen size is. But there is a remedy for this.

To find out what the screen dimensions are we can use Dimensions API.

import { Dimensions } from 'react-native';
 
const {width, height} = Dimensions.get(‘window');

Since React Native 0.61 you can also use a hook.

const {width, height} = useWindowDimensions();

Once we obtain the width from supported range screen sizes you can pick breakpoints from which your layout can change. You can provide different styles to component or hide some parts of the screen. This is similar behaviour to media queries used in CSS.

import { Text, View, Dimensions } from 'react-native';
 
class App extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      width: Dimensions.get('window').width
    };
  }
 
  render() {
    return (
     <View>
       {this.state.width < 320 ? <Text>width of the past</Text> : <Text>how big is big enough?</Text>}
     </View>
    );
  }
}

4. Detect the Platform

Apart from screen size you can also change the layout depending on which platform app is launched. To achieve this we can use the Platform module.

There are many cases where it can be used:
In component render function:

 <View>
    {Platform.OS === 'ios' ? <Text>Hi Apple!</Text> : <Text>Hi Android!</Text>}
 </View>

In styles:

cube: {
    width: Platform.OS === 'ios' ? 200 : 300,
    aspectRatio: 1
  }

The Platform module provides select method which can accept any type of args. With this flexibility we can achieve the same effect as above but cleaner code:

const Logo = Platform.select({
  ios: () => require('Apple'),
  android: () => require('Android'),
})();
 
<Logo />;

In styles:

import {Platform, StyleSheet} from 'react-native';
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {
        backgroundColor: 'red',
      },
      android: {
        backgroundColor: 'blue',
      },
    }),
  },
});

5. Device Rotation

Many apps can work in portrait and landscape mode. If this is the case for your app you have to ensure that the layout doesn’t break when changing orientation. As you can expect sometimes the layout can change drastically when you flip the device.Your components may need different styling depending on the orientation. Unfortunately, by default rotation of the device does not trigger a rerender. That’s why it has to be handled manually. We already have the knowledge required to build our own and it’s quite easy!

class RotateComponent extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      orientation: ''
    };
  }
 
  getOrientation = () => {
    if (Dimensions.get('window').width < Dimensions.get('window').height) {
      this.setState({ orientation: 'portrait' });
    } else { this.setState({ orientation: 'landscape' }); }
  };
 
  componentDidMount() {
    Dimensions.addEventListener('change', this.getOrientation);
  }
 
  render() {
    return (
      <Text>{this.state.orientation}</Text>
    );
  }
}

If you need to support orientation change across the app, it’s a good idea to use HOC to inject orientation.

const HOC = WrappedComponent => class extends PureComponent { constructor(props) { super(props); this.state = { orientation: ” }; } componentDidMount() { Dimensions.addEventListener(‘change’, this.getOrientation); } getOrientation = () => { if (Dimensions.get(‘window’).width < Dimensions.get('window').height) { this.setState({ orientation: 'portrait' }); } else { this.setState({ orientation: 'landscape' }); } }; render() { return ; } };

That’s all! You can also pass getOrientation to onLayout prop exposed by View component. It is fired on every layout change, so it should be used carefully. The decision is yours!

If you want to take advantage of the orientation in your styles, remember that it should be inline styles. We already know how to trigger a rerender of the layout when the device is rotated, but the styles are loaded only once. That’s why styles which affect the layout on rotation should be placed inline.

As you might expect, a huge React Native community already provides packages which solve many of the issues mentioned here. You can check it here or here to name just a few.