Week 2: Core Components & Flexbox Styling

Week 1 ended with one styled screen proving the setup works. This week covers the actual building blocks every React Native screen is made from — the core components, Flexbox as the sole layout mechanism (there's no CSS Grid, no floats, nothing else), and how styling actually works without a stylesheet language at all.

Phase 1 of 8 Week 2 of 22 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Use the core components — View, Text, Image, ScrollView, Pressable — correctly
  • Lay out screens with Flexbox, React Native's only layout system
  • Style components with StyleSheet.create & understand how it differs from CSS

1. The Core Components

React Native ships a small set of built-in components that render to real native UI elements on each platform. These five cover the large majority of what you'll build:

  • View — the fundamental layout container, roughly a <div>'s job
  • Text — the only component that can contain literal text
  • Image — displays images, from a local file, a bundled asset, or a remote URL
  • ScrollView — a scrollable container for content that might overflow the screen
  • Pressable — the modern way to make anything respond to touch (replaces the older TouchableOpacity/TouchableHighlight)
ProfileCard.tsx
import { Image, ScrollView, Text, View } from 'react-native';

export function ProfileCard() {
  return (
    <ScrollView>
      <View style={{ padding: 16, alignItems: 'center' }}>
        <Image
          source={{ uri: 'https://example.com/avatar.jpg' }}
          style={{ width: 80, height: 80, borderRadius: 40 }}
        />
        <Text style={{ fontSize: 18, fontWeight: '600', marginTop: 8 }}>
          Ada Lovelace
        </Text>
        <Text style={{ color: '#666' }}>Mobile Engineer</Text>
      </View>
    </ScrollView>
  );
}

Image's source prop takes either require('./local.png') for a bundled local asset, or {'{'} uri: '...' {'}'} for a remote URL — unlike the web's <img src="...">, these are two structurally different prop shapes, not the same string in both cases.

2. Text Has Real Rules

This trips up nearly everyone coming from web React: literal text must be wrapped in a Text component. Unlike HTML, where text can sit directly inside any element, a bare string inside a View throws an error:

this crashes
// Error: Text strings must be rendered within a <Text> component
function Broken() {
  return <View>Hello</View>;
}
correct
function Working() {
  return (
    <View>
      <Text>Hello</Text>
    </View>
  );
}

Text components can also nest inside each other to compose styled spans of text within one paragraph — the equivalent of a <span> inside a <p> on the web:

nested Text
<Text style={{ fontSize: 16 }}>
  Regular text, then <Text style={{ fontWeight: 'bold' }}>bold text</Text>, then regular again.
</Text>

3. Flexbox Layout

React Native has exactly one layout system: Flexbox. There's no CSS Grid, no floats, no positioning scheme beyond Flexbox and absolute positioning — and every View is a flex container by default, with flexDirection: 'column' as the default (the opposite of the web's default row):

a simple row layout
import { StyleSheet, Text, View } from 'react-native';

function StatsRow() {
  return (
    <View style={styles.row}>
      <View style={styles.stat}>
        <Text style={styles.statValue}>128</Text>
        <Text style={styles.statLabel}>Followers</Text>
      </View>
      <View style={styles.stat}>
        <Text style={styles.statValue}>42</Text>
        <Text style={styles.statLabel}>Following</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingVertical: 16,
  },
  stat: { alignItems: 'center' },
  statValue: { fontSize: 20, fontWeight: '700' },
  statLabel: { fontSize: 12, color: '#666' },
});

flex: 1 is the property you'll reach for constantly — it tells a component to grow and fill available space along the main axis, which is how most full-screen layouts anchor their root View:

a typical screen shell
const styles = StyleSheet.create({
  screen: {
    flex: 1,               // fill the entire screen
    backgroundColor: '#fff',
  },
  content: {
    flex: 1,                // fill remaining space below a header, above a footer
    padding: 16,
  },
});
The default flexDirection is the opposite of the web's

Web Flexbox defaults to row; React Native defaults every View to column. This single difference explains a lot of "why is my layout stacked when I expected a row" confusion coming from web CSS — set flexDirection: 'row' explicitly whenever you want horizontal layout.

4. StyleSheet.create

There's no CSS file, no class names, no cascade or specificity rules. Styles are plain JavaScript objects, using camelCase property names close to (but not identical to) their CSS equivalents:

inline vs. StyleSheet.create
// Works, but recreates the object on every render
<View style={{ padding: 16, backgroundColor: '#fff' }} />

// Preferred -- defined once, referenced by identity on every render
const styles = StyleSheet.create({
  card: { padding: 16, backgroundColor: '#fff' },
});
<View style={styles.card} />

// Arrays merge multiple style objects -- later entries win on conflicts
<View style={[styles.card, isActive && styles.cardActive]} />

StyleSheet.create isn't just organizational convenience — it lets React Native validate style objects once and reference them by a stable ID, rather than creating a brand-new object on every single render the way an inline style={'{{...}}'} does. The array syntax ([styles.a, condition && styles.b]) is the idiomatic way to conditionally apply styles, since there's no class-name-toggling mechanism to reach for.

5. Handling Touch

Pressable is how anything responds to a tap — there's no onClick in React Native, and nothing is clickable by default the way a web <button> or even a styled <div> can be:

a pressable card
import { Pressable, StyleSheet, Text } from 'react-native';

function ActionButton({ label, onPress }: { label: string; onPress: () => void }) {
  return (
    <Pressable
      onPress={onPress}
      style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
    >
      <Text style={styles.label}>{label}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#7c5cff',
    paddingVertical: 12,
    paddingHorizontal: 20,
    borderRadius: 8,
  },
  buttonPressed: { opacity: 0.7 },
  label: { color: '#fff', fontWeight: '600', textAlign: 'center' },
});

Passing a function to style (rather than a plain object) is a Pressable-specific pattern — it receives the current interaction state ({'{'} pressed {'}'}) and lets you compute styles from it directly, which is how the "dim slightly while held down" feedback above works with no manual state management.

6. Hands-on Exercise

Hands-on

Build a scrollable profile screen

Apply this week's core components and Flexbox to a real, complete screen layout.

Requirements:

  1. Build a screen with a profile header: an Image (use a placeholder uri), name and subtitle Text, laid out with Flexbox using alignItems to center everything.
  2. Below the header, add a horizontal stats row (at least 3 stats) using flexDirection: 'row' and justifyContent: 'space-around'.
  3. Below that, add a vertical list of at least 6 "activity" items (plain View/Text rows is fine) inside a ScrollView, and confirm the list actually scrolls once it overflows the screen.
  4. Add a Pressable "Follow" button using the function-based style prop to visibly dim on press.
  5. Move every inline style into a single StyleSheet.create call at the bottom of the file, and use the array-merge syntax for the button's pressed state.
Hint

If your row layout stacks vertically instead of horizontally, you almost certainly forgot flexDirection: 'row' — remember, View defaults to column, the opposite of the web.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

Why does <View>Hello</View> throw an error in React Native?

Unlike HTML, where any element can contain literal text, React Native requires literal text to be wrapped specifically in a Text component — View and other layout components can't render a bare string directly. This is a real rule enforced at runtime, not a style convention, and it's one of the most common early mistakes for developers coming from web React.

Q2

A row of three items is stacking vertically instead of horizontally. What's the most likely cause?

The parent View is almost certainly missing flexDirection: 'row'. Every View in React Native defaults to flexDirection: 'column' — the opposite of Flexbox's default on the web (row) — so a layout that "should" be horizontal by web-CSS instinct needs that property set explicitly here.

Q3

Why prefer StyleSheet.create({'{...}'}) over passing a plain inline object to style?

An inline style={'{{...}}'} object literal is recreated fresh on every single render, while StyleSheet.create defines the style once and lets components reference it by a stable identity afterward. It also validates the style object once upfront, catching invalid style keys earlier than an inline object would.

Q4

What does passing a function (rather than an object) to Pressable's style prop actually enable?

The function receives the component's current interaction state (such as {'{'} pressed {'}'}) as its argument, letting you compute a style directly from whether the element is currently being pressed — without wiring up your own useState to track touch-down/touch-up manually. It's a Pressable-specific convenience for exactly this common case.