Core Components
React Native has no HTML tags: the UI is built from core components that map
to native views. View is the container (div), Text is the only
place for text, Image shows pictures, TextInput is the text field,
Pressable handles taps, ScrollView scrolls. A string outside
<Text> is an error, and an image from a URL is not sized automatically: it
needs a width and height.
Applies to: React Native 0.88, Expo SDK 58.
Details
Components
| Component | iOS / Android view | Use |
|---|---|---|
View | UIView / ViewGroup | Layout container with Flexbox, styles, touch handling |
Text | UITextView / TextView | All text; nested Text is inline |
Image | UIImageView / ImageView | Local and network images |
TextInput | UITextField / EditText | Text fields; multiline for a text area |
Pressable | A View with press handling | Buttons, rows; replaces Touchable*; see Gestures |
ScrollView | UIScrollView / ScrollView | Short scrolling content; long lists go to Lists |
Modal | Presented view controller / Dialog | A layer above the whole app: dialogs, pickers |
ActivityIndicator | UIActivityIndicatorView / ProgressBar | Spinner |
Switch | UISwitch / SwitchCompat | Boolean toggle |
SafeAreaView | Deprecated, iOS only | Use react-native-safe-area-context; see Safe Area |
Button exists too, but it cannot be styled beyond color; apps build buttons
from Pressable and Text.
Text
- Every string must be inside
<Text>; otherwise the app throws "Text strings must be rendered within a<Text>component." The usual cause is{count && <Badge />}withcount === 0: React renders0as a string. - Nested
Textis inline, like<span>: the whole subtree becomes one native attributed string. AViewin a column puts eachTexton its own line. - Style inheritance works only inside a
Textsubtree: there is no global font. A sharedAppTextcomponent sets the font family and size. numberOfLineswithellipsizeMode(tailby default) truncates;onPressandselectablework on text directly.
Image
| Source | Size | Notes |
|---|---|---|
require('./logo.png') | Known at build time, sized automatically | Bundled; Metro picks @2x / @3x for the screen density |
{ uri: 'https://…' } | Must be set: width and height or aspectRatio | Without a size the image has zero size and is not visible |
{ uri: 'data:image/png;…' } | Must be set | Small inline images |
resizeMode:cover(default),contain,stretch,repeat,center,none— likeobject-fit.- Cache control is limited:
source.cacheis iOS only; GIF and WebP on Android need extra Fresco modules in a bare app. expo-imageis the usual replacement: memory and disk cache (cachePolicy),placeholder(blurhash, thumbhash),contentFit,transition, WebP / AVIF / animated GIF / SVG on both platforms,prefetch.
TextInput
- Controlled:
value+onChangeText(receives the string, not an event). Use it when the value is validated or formatted on every keystroke. - Uncontrolled:
defaultValue;onChangeTextstores the value in a ref, and it is read on submit. Fewer re-renders in large forms. - Keyboard and autofill:
keyboardType(email-address,number-pad,decimal-pad,phone-pad),secureTextEntryfor passwords (see Security),autoCompletefor password managers and SMS codes (email,current-password,new-password,one-time-code; the older iOS-onlytextContentTypedoes the same, do not set both),autoCapitalize="none"for emails. - Focus, the next field and the keyboard covering the input: Keyboard Handling.
- Labels, roles, grouping and font scaling for
Pressable,TextandTextInput: Accessibility.
Modal vs a Navigation Modal
Modal component | Navigation modal (presentation: 'modal') | |
|---|---|---|
| What it is | A view above everything, outside navigation | A screen in the navigation stack |
| Back button | Android: onRequestClose must close it | Handled by the navigator |
| URL and state | None | Has a route, deep links and params |
| Fits | A small confirm dialog, a picker, a loading overlay | A whole screen: compose, filters, sign-in |
Example
import { useState } from 'react';
import { Image, Pressable, Text, TextInput, View } from 'react-native';
export function Comment({ count }: { count: number }) {
const [text, setText] = useState('');
return (
<View style={{ flexDirection: 'row', gap: 8, padding: 16 }}>
<Image
source={{ uri: 'https://example.com/avatar.png' }}
style={{ width: 40, height: 40, borderRadius: 20 }}
/>
<View style={{ flex: 1, gap: 8 }}>
<Text numberOfLines={2}>
<Text style={{ fontWeight: 'bold' }}>Anna</Text> replied to your post
</Text>
{/* count = 0: "Text strings must be rendered within a <Text> component." */}
{count && <Text>{count} replies</Text>}
{count > 0 && <Text>{count} replies</Text>}
<TextInput
value={text}
onChangeText={setText}
placeholder="Reply"
autoCapitalize="sentences"
/>
<Pressable onPress={() => setText('')}>
<Text>Send</Text>
</Pressable>
</View>
</View>
);
}
Follow-up Questions
- Why is
Imagewith a URL blank? It has no size: network images are not measured before loading, so the layout does not jump; setwidthandheightoraspectRatio. - Can text be placed directly in a
View? No: onlyTextcreates native text views. On the web<div>text</div>works because the DOM has text nodes. ScrollVieworFlatList?ScrollViewrenders all children at once; for long or unknown-length data use a virtualized list, see Lists.- Can
requiretake a dynamic path (require(`./icons/${name}.png`))? No: Metro resolves assets at build time, so the path must be a literal; keep a map of staticrequirecalls.
Common Mistakes
{count && …}with a number:0crashes the app instead of rendering nothing.- A network
Imagewithout a size. - Styling
Button: it has nostyleprop; build aPressable. ModalwithoutonRequestCloseon Android: the back button does not close it.- Expecting
Textstyles to apply from a parentView.