Topic 6: Basics (Styles, Components, Text Inputs, Buttons, ScrollView, Activity Indicator, Images, Modals)
📖 10 min read · 🎯 intermediate · 🧭 Prerequisites: running-emulator-and-simulator, state-and-props
Why this matters
You've already written your first React Native component — but right now it just sits there, static, doing nothing. Every real app you've used on your phone — whether it's scrolling through a feed, tapping a button, filling out a login form, or watching a spinner while something loads — is built from a handful of core building blocks. This lesson is where you meet all of them at once: styles, text inputs, buttons, scroll views, activity indicators, images, and modals. Learn these eight, and you'll recognise them everywhere you look.
What You'll Learn
- Style components with
StyleSheet.createusing JavaScript objects instead of CSS strings - Build functional components for common UI patterns: text inputs, buttons, scroll views, activity indicators, images, and modals
- Control component state to toggle visibility, capture input, and show/hide loading indicators
- Compose all eight component types into a single working
App.js
The Analogy
Think of building a React Native screen the same way a stage designer furnishes a theatre set. The StyleSheet is your paint and fabric — it determines colour, size, and spacing. Individual components (TextInput, Button, Image) are the furniture pieces you place on stage. ScrollView is the stage itself when it's too long to fit in one shot — you can scroll the curtain. And Modal is the spotlight that drops a scrim over everything else so the audience focuses on one critical moment. Put them all together and you have a scene your audience can actually interact with.
Chapter 1: Understanding Styles
Styling in React Native is similar to CSS but uses JavaScript objects defined through the StyleSheet module rather than string-based class names. Properties are camelCase (backgroundColor, fontSize, borderRadius) and most length values are unitless density-independent pixels.
StylesComponent.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const StylesComponent = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Hello, Vizag!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
},
text: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
});
export default StylesComponent;
Key points:
StyleSheet.createvalidates your style objects at development time and optimises them at runtime.flex: 1tells the container to fill available space along the main axis.justifyContentcontrols alignment along the main axis;alignItemscontrols the cross axis.
Chapter 2: Creating Components
Components are the building blocks of a React Native application. Every screen you build is a tree of components — some from the RN core library, some you write yourself.
GreetingComponent.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const GreetingComponent = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Welcome to Vizag!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#e0e0e0',
borderRadius: 8,
},
text: {
fontSize: 18,
color: '#555',
},
});
export default GreetingComponent;
A functional component is simply a JavaScript function that returns JSX. Keep components small and focused — each one should do one thing well.
Chapter 3: Using Text Inputs
TextInput lets users type data into your app. Pair it with useState to keep the current value in state and reflect it back in the UI.
TextInputComponent.js
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';
const TextInputComponent = () => {
const [text, setText] = useState('');
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Enter text"
value={text}
onChangeText={setText}
/>
<Text style={styles.text}>You entered: {text}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#fff',
borderRadius: 8,
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
paddingHorizontal: 8,
},
text: {
fontSize: 16,
},
});
export default TextInputComponent;
Notable props:
| Prop | Purpose |
|---|---|
value | Controlled value — always reflects state |
onChangeText | Fires on every keystroke with the new string |
placeholder | Ghost text shown when input is empty |
paddingHorizontal | Keeps typed text from hugging the border |
Chapter 4: Adding Buttons
The Button component is the simplest way to add a tappable action. It renders a native button on each platform and fires onPress when tapped.
ButtonComponent.js
import React from 'react';
import { View, Button, Alert, StyleSheet } from 'react-native';
const ButtonComponent = () => {
const showAlert = () => {
Alert.alert('Button Pressed', 'You pressed the button!');
};
return (
<View style={styles.container}>
<Button title="Press Me" onPress={showAlert} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#fff',
borderRadius: 8,
},
});
export default ButtonComponent;
Alert.alert(title, message) pops a native OS dialog — no extra libraries needed. For fully custom styling, swap Button for TouchableOpacity or Pressable and style it yourself.
Chapter 5: Using ScrollView
When content is taller than the screen, wrap it in ScrollView. Use contentContainerStyle (not style) to style the inner scrollable area.
ScrollViewComponent.js
import React from 'react';
import { ScrollView, View, Text, StyleSheet } from 'react-native';
const ScrollViewComponent = () => {
return (
<ScrollView contentContainerStyle={styles.container}>
{Array.from({ length: 20 }).map((_, index) => (
<View key={index} style={styles.item}>
<Text>Item {index + 1}</Text>
</View>
))}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
},
item: {
padding: 20,
marginBottom: 10,
backgroundColor: '#f9f9f9',
borderRadius: 8,
},
});
export default ScrollViewComponent;
Tip:
ScrollViewrenders all children at once. For very long lists (hundreds of items), preferFlatListorSectionListwhich virtualise off-screen rows.
Chapter 6: Showing an Activity Indicator
ActivityIndicator is the standard spinning loader. Combine it with useEffect and a timeout (or a real data-fetch) to show it while content loads, then swap it out once loading is done.
ActivityIndicatorComponent.js
import React, { useState, useEffect } from 'react';
import { View, ActivityIndicator, Text, StyleSheet } from 'react-native';
const ActivityIndicatorComponent = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 3000);
}, []);
return (
<View style={styles.container}>
{loading ? (
<ActivityIndicator size="large" color="#0000ff" />
) : (
<Text>Data loaded!</Text>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
export default ActivityIndicatorComponent;
Key props:
size—"small"or"large"(or a number on Android)color— any valid colour string; here"#0000ff"for blue
The useEffect with an empty dependency array [] runs once after mount — perfect for kicking off a data load.
Chapter 7: Displaying Images
Use the core Image component with a source prop. For remote URLs, pass { uri: '...' }. Always specify explicit width and height in the style — without them the image won't render.
ImageComponent.js
import React from 'react';
import { View, Image, StyleSheet } from 'react-native';
const ImageComponent = () => {
return (
<View style={styles.container}>
<Image
style={styles.image}
source={{ uri: 'https://your-image-url.com/image.jpg' }}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
image: {
width: 200,
height: 200,
},
});
export default ImageComponent;
For local assets use source={require('./assets/photo.jpg')}. For advanced caching and performance on image-heavy screens, the community library react-native-fast-image is the go-to drop-in replacement.
Chapter 8: Using Modals
Modal renders an overlay above all other content. Control its visibility with a boolean in state, wire the open/close to buttons, and always implement onRequestClose (required on Android back-button press).
ModalComponent.js
import React, { useState } from 'react';
import { View, Text, Button, Modal, StyleSheet } from 'react-native';
const ModalComponent = () => {
const [modalVisible, setModalVisible] = useState(false);
return (
<View style={styles.container}>
<Button title="Show Modal" onPress={() => setModalVisible(true)} />
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
setModalVisible(!modalVisible);
}}
>
<View style={styles.modalContainer}>
<View style={styles.modalContent}>
<Text style={styles.modalText}>Hello, I am a modal!</Text>
<Button title="Hide Modal" onPress={() => setModalVisible(false)} />
</View>
</View>
</Modal>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
modalContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
modalContent: {
width: 300,
padding: 20,
backgroundColor: '#fff',
borderRadius: 8,
alignItems: 'center',
},
modalText: {
fontSize: 18,
marginBottom: 20,
},
});
export default ModalComponent;
animationType accepts "none", "slide", or "fade". The semi-transparent black backgroundColor: 'rgba(0, 0, 0, 0.5)' on the outer container creates the classic dimmed-backdrop effect.
Chapter 9: Combining All Components in App.js
The class's final step: import all eight components into App.js and render them together inside a top-level ScrollView.
App.js
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import StylesComponent from './StylesComponent';
import GreetingComponent from './GreetingComponent';
import TextInputComponent from './TextInputComponent';
import ButtonComponent from './ButtonComponent';
import ScrollViewComponent from './ScrollViewComponent';
import ActivityIndicatorComponent from './ActivityIndicatorComponent';
import ImageComponent from './ImageComponent';
import ModalComponent from './ModalComponent';
export default function App() {
return (
<ScrollView contentContainerStyle={styles.container}>
<StylesComponent />
<GreetingComponent />
<TextInputComponent />
<ButtonComponent />
<ScrollViewComponent />
<ActivityIndicatorComponent />
<ImageComponent />
<ModalComponent />
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
});
flexGrow: 1 on contentContainerStyle ensures the inner container expands to fill the screen even when content is short, keeping everything centred. Note the outer ScrollView wraps a nested ScrollViewComponent — this works here for demo purposes, but in production avoid deeply nested scroll views on the same axis.
graph TD
App["App.js (ScrollView)"]
App --> SC[StylesComponent]
App --> GC[GreetingComponent]
App --> TI[TextInputComponent]
App --> BC[ButtonComponent]
App --> SV[ScrollViewComponent]
App --> AI[ActivityIndicatorComponent]
App --> IC[ImageComponent]
App --> MC[ModalComponent]
🧪 Try It Yourself
Task: Build a mini profile card screen that combines three of the eight primitives.
- Create
ProfileCard.jswith:- An
Imageshowing a placeholder avatar (usehttps://via.placeholder.com/100as the URI) - A
TextInputfor the user to type their display name - A
Buttonlabelled "Save Name" that firesAlert.alert('Saved!', name)wherenameis the current input value
- An
- Import and render
<ProfileCard />insideApp.js.
Success criterion: You should see the avatar image, be able to type a name, tap "Save Name", and see a native alert that echoes back exactly what you typed.
Starter snippet:
import React, { useState } from 'react';
import { View, Image, TextInput, Button, Alert, StyleSheet } from 'react-native';
const ProfileCard = () => {
const [name, setName] = useState('');
return (
<View style={styles.card}>
<Image
style={styles.avatar}
source={{ uri: 'https://via.placeholder.com/100' }}
/>
<TextInput
style={styles.input}
placeholder="Your display name"
value={name}
onChangeText={setName}
/>
<Button
title="Save Name"
onPress={() => Alert.alert('Saved!', name)}
/>
</View>
);
};
const styles = StyleSheet.create({
card: { padding: 20, alignItems: 'center', backgroundColor: '#fff', borderRadius: 12 },
avatar: { width: 100, height: 100, borderRadius: 50, marginBottom: 12 },
input: { width: '100%', height: 40, borderColor: '#ccc', borderWidth: 1, paddingHorizontal: 8, marginBottom: 12, borderRadius: 6 },
});
export default ProfileCard;
🔍 Checkpoint Quiz
Q1. Why does React Native use StyleSheet.create instead of plain inline objects?
A) It automatically converts styles to CSS
B) It validates styles at dev time and optimises them at runtime
C) It is required — inline objects crash the app
D) It enables media queries on mobile
Q2. Given this snippet, what renders below the TextInput after typing "hello"?
const [text, setText] = useState('');
// ...
<TextInput value={text} onChangeText={setText} />
<Text>You entered: {text}</Text>
A) You entered:
B) You entered: undefined
C) You entered: hello
D) Nothing — Text requires a separate state variable
Q3. What is the bug in this ActivityIndicator usage?
const [loading, setLoading] = useState(true);
// useEffect omitted
return <ActivityIndicator visible={loading} size="large" color="#f00" />;
A) color must be a hex number, not a string
B) ActivityIndicator has no visible prop — use a conditional render instead
C) size="large" is not valid; use size={2}
D) There is no bug
Q4. You need to display 500 items from an API in a scrollable list. Which component should you choose and why?
A1. B — StyleSheet.create validates style keys and values in development and serialises the object to a native ID at runtime, reducing bridge overhead compared to recreating plain objects on every render.
A2. C — onChangeText={setText} updates text state on every keystroke, and value={text} makes the input controlled, so the Text below always mirrors the current state.
A3. B — ActivityIndicator does not accept a visible prop. The correct pattern is to conditionally render the component: {loading && <ActivityIndicator size="large" color="#f00" />} or use a ternary as shown in the lesson.
A4. Use FlatList (or SectionList if grouped). ScrollView renders all 500 children immediately, bloating memory and causing jank. FlatList virtualises off-screen rows, only keeping a window of DOM nodes in memory at once — critical for large datasets.
🪞 Recap
StyleSheet.createtakes JavaScript camelCase objects — no CSS strings — and optimises them for the native bridge.TextInputmust be controlled: bindvalueto state and update it withonChangeText.ButtontriggersonPress; pair withAlert.alertfor quick native feedback without extra libraries.ScrollViewis ideal for short, static content; useFlatListfor long or dynamic lists.ActivityIndicatoris shown/hidden via conditional rendering driven by a loading state variable.Imagerequires explicitwidthandheightstyles; remote images usesource={{ uri: '...' }}.ModalneedsonRequestClosefor Android back-button support and usestransparent + rgba backgroundfor the dimmed overlay effect.
📚 Further Reading
- React Native Core Components — official docs — the source of truth for every component covered here
- StyleSheet API — full prop reference and performance notes
- react-native-fast-image — drop-in
Imagereplacement with aggressive caching for production apps - FlatList vs ScrollView — when to graduate from
ScrollViewto a virtualised list - ⬅️ Previous: State and Props
- ➡️ Next: Basics: Picker, Status Bar, Async Storage