- Implement responsive CSS to ensure a satisfactory experience across devices.
- Use a Flat List to better handle a large amount of data.
- Use a Scroll View to create a responsive, scrollable interface.
React Native is designed for building mobile apps across Android and iOS devices. Because these devices include tablets and phones, screen sizes can vary. As such, responsive design is important when building your app. React Native uses flexbox by default, allowing content to reflow depending on the available screen space.
Unlike browsers, React Native elements do not scroll on their own when their content overflows their bounds. React Native's ScrollView component allows us to add scrolling interactions to our apps.
One downside to the ScrollView component is that it renders all child components at once, potentially impacting performance. When displaying a lot of data (several screens worth), it may be better to use the FlatList component, which lazily renders data just as it's about to appear on the screen.
This is a standard React Native project. If you haven't already, follow the Expo Go Quickstart setup guide in the React Native docs. Run npm install and then run npm start.
This lab is intended to run on an iPad simulator. After running npm start, press SHIFT + i to select a simulator. Select an iPad version from the list.
We're building an app that allows a user to find nearby cats to play with. A list of cat photos is displayed on the screen, along with their names and random distances to them. Clicking on a photo takes you to messages you've exchanged with the cat's owner.
When you first open the app, you'll notice several issues: First, the app fills only a fraction of the screen, since it was hard coded for a phone. Second, while data is visible on both screens, it is not scrollable. Several of these issues will persist throughout the lab until you arrive at the step that fixes them.
Note: By design, the app in its starting state appears "broken". In sections A and B, we'll be making the app more responsive to better fill the iPad's screen. We'll also be implementing different ways of scrolling. If the app doesn't exactly fit the screen or if it doesn't scroll, don't worry--you're not doing anything wrong! We'll be fixing each of these issues throughout, so please keep following each step through to the end.
-
Uh-oh! It looks like this app was made for a smaller screen! In
App.js, find and remove the hardcoded height and width. While React Native uses unitless values, hardcoding a height and width prevents the app from adjusting its content to fill the screen. You'll notice that the app disappears, but we'll take care of that in the next step. -
React Native uses flexbox to manage responsiveness; this is enabled by default. One of the most useful CSS properties to remember when working on a React Native project is flex, which tells an element how to grow or shrink to fill available space. We can use the
flexproperty to have the app fill the space responsively. InApp.js, give both thecontainerandnavigationContaineraflexproperty of1in thestylesobject.
Click here for the coded answer
const styles = StyleSheet.create({
container: {
flex: 1,
},
navigationContainer: {
flex: 1,
}
})-
Hardcoded dimensions should be used judiciously, usually in cases where a specific fixed width or height is desired. In this case, we want the title to take up a certain amount of space. Add a height property to the
TitleBarcomponent so that it takes up more space and is more easily visible. -
You might notice that the title overlaps the statuses at the top of the screen. While adding padding or margin could work, React Native includes a component that accounts for this space. In
App.js, importSafeAreaViewfrom React Native and wrap the entire app in theSafeAreaView. Give it a style offlex: 1. Note how the space is now accounted for. Check out the docs for more info.
Click here for the coded answer
export default function App() {
return (
<SafeAreaView style={styles.safeAreaView}>
<View style={styles.container}>
{ /* ... */ }
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
navigationContainer: {
flex: 1,
},
safeAreaView: {
flex: 1,
}
})- The
FlatListcomponent is useful for rendering large amounts of data in a performant manner. It has benefits and drawbacks that are important to note. First, theFlatListrenders data lazily, meaning it won't try to render data before it's needed. One of the main drawbacks, though, is responsiveness, particularly with grids. In ourCatScreenpage, we want to flow the photos of the cats into a grid shape and have this grid fill the available space. In a typical React application, we'd useflex-wrap: wrapto have the content flow as needed. Unfortunately, with aFlatList, theflex-wrapproperty is not supported and React Native will warn you about this.
Check out the React Native docs for more info on flat lists!
- The
FlatListcomponent does include anumColumnsprop which allows you to specify the number of columns for the content. TheFlatListcomponent will flow its content into these columns. But this presents one significant drawback. What do you think this might be? Think about it and then click below for the answer.
Click here for the answer
Much like using hardcoded heights and widths, using a hardcoded numColumns prop won't work across different screen sizes. Fortunately, we can work around this! Read on to find out.
-
In the
CatScreencomponent, import theFlatListcomponent from React Native. TheFlatListcomponent does not take children. Instead, it takes adataprop, which is an array of data it will use for each element, as well as arenderItemprop to render the scrollable content. TheFlatListwill iterate over each item in thedataarray and call therenderItemfunction for it. TherenderItemfunction will receive an object with the key ofitem. Thisitemwill have the data to display. TherenderItemfunction should use thisitemand return a component to display. See the React Native docs for more info. -
In the
CatScreen, write arenderItemfunction that returns thePhotoCardcomponent. You can use thisitemargument given by theFlatListto populate thePhotoCardprops.
Click here for the coded answer
const renderItem = ({ item }) => {
return (
<PhotoCard name={item.name} photo={item.photo} distance={`${item.distance} miles`} />
);
};- In
CatScreen, replace the currentViewand map function with theFlatList. Pass in thecatDataobject and therenderItemfunction you just created. The elements will still appear as a single column, but that's okay! We'll take care of that shortly.
Click here for the coded answer
return (
<FlatList
data={catData}
renderItem={renderItem}
/>
)- Serialized elements in React require a
keyprop. TheFlatListcomponent can extract a key and pass it down automatically with itskeyExtractorprop. This prop takes a function that receives the current item as its first argument and the index as its second. It returns a unique string to use as a key. Write a function for thekeyExtractorprop that takesdataandindexarguments and returns a unique key. Add it to theFlatList. (HINT: Use a combination of the cat's name and an index for the key!)
Click here for the coded answer
keyExtractor={(data, index) => `${data.name}-${index}`}- You should be seeing the list of cats rendered as a single column on the page. This is great, but it's not an ideal user experience, especially with so much empty space on the screen. We can use the
numColumnsprop to reflow the content into columns. Try adding anumColumnsprop of3to theFlatList. As discussed earlier, this presents issues with responsiveness, since hardcoding the value means we'll have three columns no matter the size of the screen. How might we solve for this? Think about it for a few seconds and then check out the answer below.
Click here for the answer
If we divide the width of the space by the width of each PhotoCard element and round down, we'll get the number of columns.
- First, we'll need to know the width of each item we want to display. In this case, each
PhotoCardis hardcoded to be200pixels wide. Next, we'll need the width of the space to fill. We have a few options, likeuseRef, but let's go with something more React Native-y. React Native includes a useWindowDimensions hook that returns an object with the height and width of the screen size. This is handy for responsiveness! In theCatScreen, divide thewidthreturned byuseWindowDimensionsby200and round down usingMath.floor. Provide that value to thenumColumnsprop. The cats should fill the screen!
Note: You might get an error about changing the
numColumnson the fly. If you do, reload the app by clickingDevice->Shake->Reload. You can also comment thekeyExtractor, save, and then uncomment it.
Click here for the coded answer
const CatScreen = () => {
const { width } = useWindowDimensions();
const renderItem = ({ item }) => {
return (
<PhotoCard name={item.name} photo={item.photo} distance={`${item.distance} miles`} />
);
};
return (
<View style={styles.container}>
<FlatList
data={catData}
renderItem={renderItem}
keyExtractor={(item, index) => `${item.name}-${index}`}
numColumns={Math.floor(width / 200)}
/>
</View>
);
};-
We can't simply add
overflowto an element as we might on a web app to enable scrolling. Instead, we use React Native'sScrollViewcomponent to enable scrollable screens in an app.ScrollViewcomponents have a bit more versatility than theFlatListcomponent, but they also come with a performance cost. You can use theFlatListorScrollViewas needed. For now, let's add aScrollViewto theMessagesScreen. Check out the docs if needed. -
In the
MessagesScreen, import theScrollViewfrom React Native and wrap it around theView.
Click here for the coded answer
const MessageScreen = () => {
return (
<ScrollView>
<View style={styles.container}>
{messageData.map((message, idx) => {
return (
<Message message={message} key={`${message.text}-${idx}`} />
);
})}
</View>
</ScrollView>
);
};- Note that you're now able to scroll the messages in the simulator!
Mild
- The
FlatListcomponent can be customized with a wide variety of props, like column styles, headers, and footers. Check out the docs and try customizing yourFlatListimplementation.
Medium
- Updating the
FlatListcomponent's data prop causes the component to rerender. Can you add a filter button above the cat photos that filters the array by distance? You can use React Native's Button, TouchableOpacity, or Pressable components. ThePressablecomponent is the most recent touchable component. This might be a great chance to experiment with the subtle differences between each!
Spicy
- The
ScrollViewcomponent can be paginated! Implement aScrollViewinstead of aFlatListin theCatScreen. Refer to the React Native docs to turn theScrollViewinto a paginated component that shows one cat at a time. Note that thePhotoCardcomponent will need to take up the full screen for this to work. You can use the React Native useWindowDimensions hook to get the height and width of the screen or a ref.