EduFlow

Section 1 · The Mental Model

Core Components: View, Text, Image, ScrollView

20 min3 min readSpot the bug

Lesson at a glance

Build Trailhead’s hike detail screen with View, Text, Image and ScrollView, and avoid the text and image rules that crash or blank out mobile screens.

  1. 1View: a box, nothing more
  2. 2Text: the only place for strings
  3. 3Image: tell it how big
  4. 4ScrollView: content that may not fit
  5. 5The rest of the core set

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

On the web, you can throw a string into a div, set a font on body, and drop in an <img> without thinking about its size. Try the same in React Native and you’ll get a red error screen, text in the wrong font, or an image that never appears. Four components carry most screens, and each has one rule that catches web developers.

View: a box, nothing more

View is the building block for layout: a rectangle that can have a background, a border, padding and children. It maps to a native container view. It has no text node, so it cannot hold strings:

TSX
// Throws: Text strings must be rendered within a <Text> component.
<View>Ridge Loop</View>

// Works
<View>
  <Text>Ridge Loop</Text>
</View>

Think of View as the div you use for layout, minus everything a div does with text. It’s also the thing you style most: cards, rows, dividers, badges and spacers are all Views with a background, a border radius or some padding. Views are cheap, but not free; each one becomes a real native view, so a list row with twelve nested wrappers costs more than one with four. Flatten when a wrapper adds nothing.

Text: the only place for strings

Every visible string goes inside <Text>. Nested <Text> elements become one paragraph, and that’s also the only place style inheritance exists: a child Text inherits font, color and size from a parent Text.

TSX
<Text style={{ fontSize: 16, color: '#333' }}>
  Ridge Loop is <Text style={{ fontWeight: '700' }}>8.4 km</Text> with 420 m of climbing.
</Text>

A View never passes text styles down. If you want a consistent font across the app, make a component (an AppText that sets your defaults) rather than hunting for a global stylesheet that doesn’t exist.

Text also scales with the user’s accessibility settings. Someone who has set a larger font size in iOS or Android settings gets bigger text in your app automatically, which is good, and which means a fixed-height box around text will eventually clip it. Leave room to grow; prefer padding over fixed heights for anything that contains words.

Two props you’ll use constantly: numberOfLines={2} truncates with an ellipsis (perfect for list cards), and selectable lets users copy the text.

Image: tell it how big

For images that ship with the app, require() resolves the file at build time, so React Native knows the image’s dimensions:

TSX
<Image source={require('../../assets/images/trail-placeholder.png')} />

For images from the network, React Native can’t know the size until the download finishes, so you must give one. Without it the image renders at 0 by 0, silently.

TSX
<Image
  source={{ uri: hike.photoUrl }}
  style={{ width: '100%', height: 220, borderRadius: 12 }}
  resizeMode="cover"
  accessibilityLabel={`Photo from ${hike.name}`}
/>

resizeMode works like CSS object-fit: cover fills and crops, contain fits inside. For production apps, many teams use expo-image instead: it has the same basic shape, adds disk caching and placeholders, and uses contentFit in place of resizeMode.

ScrollView: content that may not fit

Native views don’t scroll on overflow. Scrolling is its own component, ScrollView, and it has two quirks.

First, it needs a bounded height: it scrolls within whatever space its parent gives it, so the parent chain needs flex: 1 (you’ll see why in the flexbox lesson). Second, padding and alignment for the scrolled content go in contentContainerStyle, not style. style sizes the scrolling window; contentContainerStyle styles the content that slides inside it.

Here is Trailhead’s detail screen, with sample data for now:

src/app/hike.tsx
import { Image, ScrollView, Text, View } from 'react-native';

const hike = {
  name: 'Ridge Loop',
  photoUrl: 'https://images.example.com/ridge-loop.jpg',
  distanceKm: 8.4,
  elevationM: 420,
  notes: 'Steep first kilometre, then a long ridge with views of the lake. Bring water: the spring at the saddle was dry.',
  photoCount: 0,
};

export default function HikeScreen() {
  return (
    <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 16, gap: 12 }}>
      <Image
        source={{ uri: hike.photoUrl }}
        style={{ width: '100%', height: 220, borderRadius: 12 }}
        accessibilityLabel={`Photo from ${hike.name}`}
      />
      <Text style={{ fontSize: 28, fontWeight: '700' }}>{hike.name}</Text>
      <View style={{ flexDirection: 'row', gap: 16 }}>
        <Text>{hike.distanceKm} km</Text>
        <Text>{hike.elevationM} m up</Text>
      </View>
      {hike.photoCount > 0 && <Text>{hike.photoCount} photos</Text>}
      <Text style={{ fontSize: 16, lineHeight: 24 }}>{hike.notes}</Text>
    </ScrollView>
  );
}

Look at what isn’t there: no div, no class names, no stylesheet import, and no stray strings. Numbers like hike.distanceKm are inside Text, so they render fine.

The rest of the core set

You’ll meet the others as Trailhead needs them: Pressable and TextInput in the next lesson, FlatList and SectionList for lists, ActivityIndicator for loading, Switch for toggles, Modal for overlays, and KeyboardAvoidingView for forms. Each maps to a native component, so they look and behave like the platform by default.

Next you’ll make Trailhead interactive: buttons that respond to touch and inputs that handle typing.

Key takeaways

  • View is a layout box and can’t hold raw text; every string must be inside a <Text>.
  • Text styles inherit only from a parent <Text>, never from a <View>.
  • Network images need an explicit width and height; local images from require() know their own size.
  • ScrollView renders all of its children at once and needs a bounded height, so use it for screens of content, not long data lists.
  • {count && <Text>…</Text>} crashes when count is 0, because 0 is rendered as a raw string; compare explicitly instead.

Further reading

Check your understanding

4 questions

Question 1 of 4

Which line throws “Text strings must be rendered within a component”?

TSX
<View>
  <Text>Ridge Loop</Text>
  {hike.photoCount && <Text>{hike.photoCount} photos</Text>}
</View>
Question 2 of 4

You set style={{ color: '#2f6f4e', fontSize: 18 }} on a View. What happens to the Text inside it?

Question 3 of 4

<Image source={{ uri: hike.photoUrl }} /> shows nothing, and there are no errors. What’s the most likely fix?

Question 4 of 4

Trailhead’s detail screen has a photo, a title, stats and a long description that may not fit. Which container fits best?

Practice

Spot the bug

Find the two crashes in the hike summary

This HikeSummary component is shown for every hike in Trailhead. It works for some hikes and shows the red error screen for others, always with Text strings must be rendered within a component. Select the two lines that can cause that error.

Select the line(s) you think are wrong, then check.