Setting Max Width When Supporting Web and Mobile in React Native
Tags: reactnative, reactnativeweb
Published: 2022-08-29 21:03:41

---
title: Setting Max Width When Supporting Web and Mobile in React Native
published: true
description: Learn how to customize your React Native app's layout to work well on both portrait and landscape screens - perfect for running React Native on the web!
tags: reactnative, reactnativeweb
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
canonical_url: https://www.reactnativeschool.com/setting-max-width-when-supporting-web-and-mobile-in-react-native
---


[React Native's Web support continually gets better](https://necolas.github.io/react-native-web/docs/). With that, we've got to consider different screen sizes and orientations.

While building a React Native version of the game "Match the Pairs" ([100% open source](https://github.com/ReactNativeSchool/match-the-pairs-react-native)) I found that when playing the game in a horizontal landscape on the web everything looked off. The cards were wider than they were tall - I want to maintain the traditional playing card shape on the game board, regardless of screen size.

![App running on the web without bounds](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ci0zhk0picyz9lz77rb.png)

_[Originally published on React Native School](https://www.reactnativeschool.com/setting-max-width-when-supporting-web-and-mobile-in-react-native)_

## The Reality of Supporting Multiple Platforms

Before diving into the code I want to make sure you have proper expectations around targeting multiple platforms with a single codebase.

Often when I talk to people about the universal nature of React Native there is an assumption that you get to support iOS, Android, and the Web from _one code base with 100% code sharing_.

That is unrealistic. You can absolutely support iOS, Android, and the Web from a single codebase that is **MOSTLY shared... but there will be differences!**

But each platform is going to have unique requirements to make it fit the platform the user is running it on.

This sometimes requires customizations to your layouts and code. That's not a bad thing - you're still able to share a TON of code!

This simple app is a great example - this article shows layout change requirements and a previous article [outlines the differences between mobile and the web when handling sharing](https://www.reactnativeschool.com/how-to-share-from-a-react-native-app-including-the-web).

Now, with that out of the way lets dive into the code.

## The Starting Code

First, lets look at the code that drives the current UI.

```tsx
import { SafeAreaView, StyleSheet, View, Text } from "react-native"
import { StatusBar } from "expo-status-bar"

import { StatsCard, GameCard } from "components/cards"
import { Button } from "components/buttons"

import { Spacing, Colors } from "constants/index"

import { useMatchGame } from "hooks/useMatchGame"
import { shareGame } from "src/utils/shareGame"

const ROWS = [
  [0, 1, 2, 3],
  [4, 5, 6, 7],
  [8, 9, 10, 11],
  [12, 13, 14, 15],
]

const MatchThePairs = () => {
  const {
    emojis,
    reset,
    chooseCard,
    activeCardIndex,
    matchedCards,
    comparisonCards,
    totalMoves,
    matchCount,
    totalPairs,
  } = useMatchGame()

  const handlePress = (index: number) => {
    chooseCard(index)
  }

  const handleShare = () => {
    if (matchCount === totalPairs) {
      shareGame({ emojis, moveCount: totalMoves })
    } else {
      alert("You haven't matched all the pairs yet!")
    }
  }

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar style="dark" />
      <View style={[styles.row, styles.header]}>
        <Text style={styles.headerText}>Match the pairs 🤔</Text>
      </View>

      <View style={[styles.row, styles.stats]}>
        <StatsCard
          title="Pairs matched"
          numerator={matchCount}
          denominator={totalPairs}
        />
        <StatsCard title="Total moves" numerator={totalMoves} />
      </View>

      {ROWS.map((indices, rowIndex) => (
        <View style={[styles.row, styles.gameRow]} key={rowIndex}>
          {indices.map(emojiIndex => {
            const inMatchedCard = matchedCards.includes(emojiIndex)
            const cardIsVisible =
              inMatchedCard || comparisonCards.includes(emojiIndex)

            return (
              <GameCard
                key={emojiIndex}
                index={emojiIndex}
                emojis={emojis}
                onPress={() => handlePress(emojiIndex)}
                selected={activeCardIndex === emojiIndex}
                visible={cardIsVisible}
                disabled={inMatchedCard}
              />
            )
          })}
        </View>
      ))}

      <View style={[styles.row, styles.actions]}>
        <Button type="primary" onPress={() => reset()}>
          Reset game
        </Button>
        <Button onPress={handleShare}>Share game</Button>
      </View>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: Spacing.lg,
  },
  headerText: {
    color: Colors.greyDarkest,
    fontSize: 20,
    fontWeight: "600",
    marginTop: Spacing.md,
    marginBottom: Spacing.xl,
  },
  row: {
    flexDirection: "row",
    marginHorizontal: Spacing.sm,
  },
  gameRow: {
    flex: 1,
  },
  header: {
    marginHorizontal: Spacing.md,
  },
  stats: {
    marginBottom: Spacing.lg,
  },
  actions: {
    justifyContent: "center",
    marginTop: Spacing.xl,
  },
})

export default MatchThePairs
```

## Layout for Web and Mobile in React Native

To fix the issue we'll do 3 things

1. Wrap the game board in a `View` with the `content` styles
2. Set the `content` to be full width but have a max width value
3. Center the `content` view within the container.

```tsx
/* ... */

const MatchThePairs = () => {
  const {
    emojis,
    reset,
    chooseCard,
    activeCardIndex,
    matchedCards,
    comparisonCards,
    totalMoves,
    matchCount,
    totalPairs,
  } = useMatchGame()

  const handlePress = (index: number) => {
    /* ... */
  }

  const handleShare = () => {
    /* ... */
  }

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.content}>
        <StatusBar style="dark" />
        <View style={[styles.row, styles.header]}>
          <Text style={styles.headerText}>Match the pairs 🤔</Text>
        </View>

        <View style={[styles.row, styles.stats]}>
          <StatsCard
            title="Pairs matched"
            numerator={matchCount}
            denominator={totalPairs}
          />
          <StatsCard title="Total moves" numerator={totalMoves} />
        </View>

        {ROWS.map((indices, rowIndex) => (
          <View style={[styles.row, styles.gameRow]} key={rowIndex}>
            {indices.map(emojiIndex => {
              const inMatchedCard = matchedCards.includes(emojiIndex)
              const cardIsVisible =
                inMatchedCard || comparisonCards.includes(emojiIndex)

              return (
                <GameCard
                  key={emojiIndex}
                  index={emojiIndex}
                  emojis={emojis}
                  onPress={() => handlePress(emojiIndex)}
                  selected={activeCardIndex === emojiIndex}
                  visible={cardIsVisible}
                  disabled={inMatchedCard}
                />
              )
            })}
          </View>
        ))}

        <View style={[styles.row, styles.actions]}>
          <Button type="primary" onPress={() => reset()}>
            Reset game
          </Button>
          <Button onPress={handleShare}>Share game</Button>
        </View>
      </View>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingVertical: Spacing.lg,
    alignItems: "center",
  },
  content: {
    flex: 1,
    width: "100%",
    maxWidth: 550,
  },
  // ...
})

export default MatchThePairs
```

Let's dive into _why_ we do each of these things.

First, we wrap our board in a second `View` so that the `SafeAreaView` can still take up the full screen (allowing for any background changes or other full screen changes).

We then use `styles.content` to fill the full vertical height, set the width to be 100% on portrait screens, and set a max width on it so that the playing cards remain the correct shape.

Finally we add `alignItems: 'center'` to the `styles.container` to center our game board. You can accomplish the same thing by adding `marginHorizontal: 'auto'` to `styles.content` but I prefer to use Flexbox whenever possible.

All of that results in the following game on the web:

![App running on the web with bounds](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5faix9xgicpi94xpkh4o.png)

You can find the full [open source React Native game](https://github.com/ReactNativeSchool/match-the-pairs-react-native) on Github.