Building screens: layout, styling and lists
Build the Brew & Bean menu screen with flexbox layout, StyleSheet styling, a reusable component, search and category filters, and a FlatList that stays fast with long lists.
You will build the Brew & Bean menu screen: a scrolling list of items with name, category and price, a search box and category chips that filter the list. Along the way you learn flexbox layout, styling, reusable components and FlatList, the component that makes long lists fast.
Prerequisites: the previous lesson (a running Expo app with app/index.js), and basic React (props, state).
Layout with flexbox
Every View lays out its children with flexbox, as on the web, with two differences: the main axis is vertical by default (flexDirection: "column"), and there are no units. Sizes are plain numbers measured in density-independent pixels, so they look about the same size on different phones.
The properties you will use most:
Flexbox properties for React Native
| Property | What it does | Example |
|---|---|---|
| flex | How much of the free space this box takes | flex: 1 fills the parent |
| flexDirection | Direction of children | "row" puts children side by side |
| justifyContent | Spacing along the main axis | "space-between", "center" |
| alignItems | Alignment on the cross axis | "center", "flex-start" |
| gap | Space between children | gap: 8 |
| padding / margin | Inside and outside space | padding: 16, marginTop: 8 |
Other style rules: colours are strings such as "#4a2c17", text style goes on Text (not on View), and styles do not cascade from parent to child as they do in CSS. A Text inside another Text does inherit text style, which is the one exception.
Plan
- 1Add the menu data in a data file.
- 2Build a reusable MenuItem component.
- 3Build the screen with search, chips and a FlatList.
- 4Run it and check each filter.
Step 1: The data
We need a menu. Create a folder data at the top of your project (next to app) and add this file. The ids, names and prices are the Brew & Bean menu used across Tech Tracks.
export const MENU = [
{ id: "espresso", name: "Espresso", category: "coffee", price: 120 },
{ id: "cappuccino", name: "Cappuccino", category: "coffee", price: 180 },
{ id: "cold-brew", name: "Cold Brew", category: "coffee", price: 220 },
{ id: "filter-coffee", name: "Filter Coffee", category: "coffee", price: 80 },
{ id: "masala-chai", name: "Masala Chai", category: "tea", price: 90 },
{ id: "croissant", name: "Butter Croissant", category: "bakery", price: 150 },
{ id: "banana-bread", name: "Banana Bread", category: "bakery", price: 130 },
{ id: "veg-sandwich", name: "Veg Grilled Sandwich", category: "food", price: 160 },
];
export const CATEGORIES = ["all", "coffee", "tea", "bakery", "food"];Later lessons replace this with data from an API.
Step 2: A reusable menu row
Splitting the UI into small components works exactly as in React for the web. Create components/MenuItem.js.
import { View, Text, StyleSheet } from "react-native";
export default function MenuItem({ item }) {
return (
<View style={styles.row}>
<View style={styles.info}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.category}>{item.category}</Text>
</View>
<Text style={styles.price}>₹{item.price}</Text>
</View>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "white",
padding: 16,
borderRadius: 12,
marginBottom: 10,
},
info: { flex: 1 },
name: { fontSize: 18, fontWeight: "600", color: "#2b1a0e" },
category: { fontSize: 14, color: "#8a6f5a", textTransform: "capitalize" },
price: { fontSize: 18, fontWeight: "bold", color: "#4a2c17" },
});flexDirection: "row" puts the name block on the left and the price on the right. flex: 1 on the info block lets it take the remaining space.
Lists: FlatList, not map
On the web you may render a list with items.map(...). On a phone, that builds every row up front, which is slow and uses a lot of memory for hundreds of items. FlatList renders only the rows near the screen and recycles them as you scroll.
It needs three props: data (an array), renderItem (a function that receives { item } and returns a row) and keyExtractor (returns a unique string for each item).
ScrollView renders all its children at once. It is fine for a short screen such as a settings page or a form. Use FlatList for anything that can grow, such as orders, messages or search results.
Step 3: The menu screen
Replace app/index.js with the screen below. It keeps two pieces of state: the text in the search box and the selected category. The filtered list is computed from them on every render.
import { useState } from "react";
import { View, Text, TextInput, FlatList, Pressable, StyleSheet } from "react-native";
import MenuItem from "../components/MenuItem";
import { MENU, CATEGORIES } from "../data/menu";
export default function Home() {
const [query, setQuery] = useState("");
const [category, setCategory] = useState("all");
const items = MENU.filter(
(m) =>
(category === "all" || m.category === category) &&
m.name.toLowerCase().includes(query.trim().toLowerCase())
);
return (
<View style={styles.container}>
<Text style={styles.title}>Brew & Bean</Text>
<TextInput
style={styles.search}
placeholder="Search the menu"
value={query}
onChangeText={setQuery}
autoCorrect={false}
/>
<View style={styles.chips}>
{CATEGORIES.map((c) => (
<Pressable
key={c}
onPress={() => setCategory(c)}
style={[styles.chip, category === c && styles.chipActive]}
>
<Text style={[styles.chipText, category === c && styles.chipTextActive]}>
{c}
</Text>
</Pressable>
))}
</View>
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <MenuItem item={item} />}
ListEmptyComponent={<Text style={styles.empty}>No items match your search.</Text>}
keyboardShouldPersistTaps="handled"
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: "#fff8f0", paddingHorizontal: 16, paddingTop: 56 },
title: { fontSize: 28, fontWeight: "bold", color: "#4a2c17", marginBottom: 12 },
search: {
backgroundColor: "white",
borderRadius: 10,
paddingHorizontal: 14,
paddingVertical: 10,
fontSize: 16,
marginBottom: 12,
},
chips: { flexDirection: "row", flexWrap: "wrap", gap: 8, marginBottom: 14 },
chip: { paddingVertical: 6, paddingHorizontal: 14, borderRadius: 20, backgroundColor: "#ead9c8" },
chipActive: { backgroundColor: "#4a2c17" },
chipText: { color: "#4a2c17", textTransform: "capitalize" },
chipTextActive: { color: "white" },
empty: { textAlign: "center", color: "#8a6f5a", marginTop: 24 },
});onChangeText={setQuery} passes each typed string to state, so the list filters as you type. paddingTop: 56 keeps the title below the status bar; the next lesson adds a proper header.
A title, a search box, five chips (all, coffee, tea, bakery, food) and a list of white cards. Tap "bakery" and only Butter Croissant and Banana Bread remain. Type "chai" with "all" selected and only Masala Chai shows.Styling tips
Reuse styles. Put colours in one file such as constants/colors.js once your app grows, instead of repeating hex codes.
Combine styles with an array. style={[styles.chip, active && styles.chipActive]} applies the later style on top. A false value is ignored.
Respect the safe area. Phones have notches, rounded corners and gesture bars. The SafeAreaView from react-native-safe-area-context (installed with Expo Router) keeps content out of those areas. When you use a navigation header, as in the next lesson, the header already handles the top.
Check both platforms and small screens. A layout that fits on your phone may overflow on a smaller one. Prefer flex and flexWrap over fixed widths.
Images and touch feedback
Use Image from react-native for pictures. A local file is source={require("../assets/images/logo.png")}. A remote one is source={{ uri: "https://..." }} and needs a width and height in its style, because React Native cannot know the size of a network image in advance.
Pressable can change its look while pressed: give style a function, for example style={({ pressed }) => [styles.button, pressed && { opacity: 0.7 }]}.
Common mistake
Do not nest a FlatList inside a vertical ScrollView. Both try to scroll and React Native warns about it, and the list loses its speed advantage. Put the other content in the ListHeaderComponent prop of the FlatList instead.
I am learning React Native. Here is a screen component and a screenshot description of what I see.
[paste your component]
I want: [describe the layout you want, for example: price on the right, name on the left].
What I get: [describe what happens].
Explain which style props cause the difference and show the smallest fix. Do not rewrite the whole file.What to expect: A good answer points at specific style props such as flexDirection, alignItems or flex, and shows a small diff. Test the suggested change on your own phone.
Try it: Add sorting and a total
Add two buttons above the list: "Price low to high" and "Price high to low". Show the number of items shown below the title (for example "5 items"). Make the sort a piece of state.
Key takeaways
- React Native layout is flexbox with a column main axis by default and unitless numbers.
- Style with StyleSheet.create objects; combine styles with arrays; text styles go on Text.
- Use FlatList (data, renderItem, keyExtractor) for lists; ScrollView only for short, fixed content.
- TextInput with value and onChangeText gives you controlled inputs like on the web.
- Never put a FlatList inside a vertical ScrollView.
Check yourself
Finishing the quiz marks the lesson as done.
1. What is the default flexDirection in React Native?
2. Why use FlatList instead of items.map(...) for a long list?
3. Which prop tells FlatList how to identify each row?
4. You want the name on the left and the price on the right in one row. Which styles on the parent do that?
5. Why copy an array with [...items] before calling sort?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.