React Basics
React Basics
📌 Section Objective:
Understand and apply the fundamentals of React Native by building a small Goal Tracker App.
All these are essential building blocks for every React Native app.
Component Purpose
View Like a <div> in HTML — used for layout & grouping
Text To display text
TextInput To accept user input
Button A clickable button
FlatList Efficiently render scrollable lists
ScrollView Scrollable container for child components
Modal To show overlay content like popups
These components use JavaScript + JSX syntax and are rendered into native UI elements under the
hood — that’s what makes React Native powerful!
🎨 2. Styling React Native Apps
Unlike web development, React Native does not use CSS files. Instead, it uses a built-in object-based
styling system.
Values (like padding, margin, fontSize) are not unit-based (px is not used). Just use
numbers.
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0'
}
});
React Native’s styling engine is optimized for mobile apps and mimics CSS concepts (like Flexbox),
but with a JavaScript-based API.
This is achieved using React's useState() hook — a key React concept that works in React Native
too.
🧠 State Example:
const [goals, setGoals] = useState([]);
function addGoalHandler(newGoalText) {
setGoals((currentGoals) => [...currentGoals, newGoalText]);
}
When a user clicks a button, you update the state → this re-renders the UI with updated data.
✨ Interactivity Elements:
Though this app is simple, it mirrors the fundamentals you'll use in any real-world app:
User input
Updating UI based on changes
Component re-rendering
List rendering and manipulation
Styling and layout control
🔧 Tools Used
Expo CLI for development and testing
VS Code (recommended editor)
Expo Go App (to preview on your device)
🧠 Section Overview:
This section covers:
🛠 Key Steps:
Open terminal/command prompt: This can be the built-in terminal in VS Code or any other
terminal app.
Install dependencies:
npm install
This launches the Expo dev server, which watches your code and hot-reloads changes.
▶️Run on emulator:
Press:
o for Android emulator
a
o for iOS simulator (macOS only)
i
This will open your app in your preview devices.
Why Expo?
Expo is a development toolchain that simplifies running and building React Native apps. It handles a
lot of the native code and configuration for you, especially in the early stages.
In this course:
🧠 Why Hooks?
Hooks simplify logic reuse and improve readability. They also match the future direction of React and
React Native.
Component Purpose
View Container (like <div>)
Text Render text (like <p> or <span>)
Button Basic button UI
TextInput User input field
Image To show images
ScrollView Makes content scrollable
FlatList Efficient way to render long lists
Modal Display overlay UI like popups
import { View, Text, StyleSheet } from 'react-native';
🧠 Native devices don’t have HTML or a DOM. React Native translates these components to real
Android/iOS UI widgets.
🖼 4. Styling in React Native
React Native does not use CSS files. Instead, you style with JavaScript objects using
StyleSheet.create.
🔑 Syntax Example:
const styles = StyleSheet.create({
container: {
padding: 16,
backgroundColor: '#fff'
},
text: {
fontSize: 18,
color: 'blue'
}
});
<View style={styles.container}>
<Text style={styles.text}>Welcome!</Text>
</View>
Units like px, em are not used. Numbers are treated as density-independent pixels (dp).
Styling uses a subset of CSS (mainly Flexbox for layout).
Even though the syntax is similar, React Native renders to native UIs, not to a browser DOM.
🧩 7. Composing Components
Just like React web apps:
🛠 Example:
function GoalItem(props) {
return <Text>{props.text}</Text>;
}
function App() {
return (
<View>
<GoalItem text="Learn React Native" />
</View>
);
}
Code snippet
Cmd: npx create-expo-app --template blank 00-starting-project
App.js:
--
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
React Native comes with a limited set of core components that replace HTML elements. Every UI
you build will be composed using these. They are the "building blocks" of native UIs.
🧠 Key Rule:
In React Native, plain strings can't be rendered directly — they must be wrapped inside a <Text>
component.
❌ Invalid:
<View>
Hello World
</View>
✅ Valid:
<View>
<Text>Hello World</Text>
</View>
Unlike web development, native platforms require strict separation of text and containers.
Think of React Native as more structured than the web. You must use the correct component for
the correct job.
🏗️2. Nesting & Structuring UI
🧱 Component Nesting:
<View>
<Text>Title</Text>
<Text>Subtitle</Text>
</View>
This is like:
🧠 Rule of Thumb:
Component Purpose
View Layout container for grouping other components
Text For displaying any readable text
Image For showing pictures
TextInput For getting input from users
Button For user interactions (taps)
The hierarchy and structure you build with View and Text is essential to layout design and
interaction flow.
✅ This is completely valid. Just like nesting <div>s or <section>s in HTML, you can nest Views in
React Native to create layout groups, sections, cards, etc.
🖼️4. More Core Components: The React Native Toolbox
React Native gives you only a few components, but they are powerful and composable.
Most Used:
Component Role
Text Display strings
View Group/position components
TextInput Form input fields
Button Interactivity
Image Show pictures
ScrollView Make a screen scrollable
FlatList Display long lists efficiently
TouchableOpacity, Pressable Tappable areas with custom feedback
🔗 Importing:
import { Button } from 'react-native';
Unlike web React (where <button> is globally available), React Native components must be
explicitly imported.
🧠 Native apps don’t have HTML tags — React Native provides platform-appropriate APIs instead.
✅ Syntax:
<Button title="Click Me" />
❌ Invalid:
<Button>Click Me</Button>
✅ Cross-Platform Styling:
This means your UI feels native on both platforms out of the box.
You don’t have to manually detect the OS to change button styles — React Native handles it.
To make it interactive:
<Button
title="Click Me"
onPress={() => {
console.log('Button was pressed!');
}}
/>
🧠 Key Principle:
You build React Native apps by combining and nesting core components to make more complex,
reusable components.
Example:
function WelcomeScreen() {
return (
<View style={styles.screen}>
<Text style={styles.title}>Welcome to the App</Text>
<Button title="Get Started" onPress={startApp} />
</View>
);
}
This is exactly how real apps are built: from simple components → structured UIs → full screens →
full app.
Code Snippet
EX: npx create-expo-app --template blank 01-working-with-core-components
App.js
import { StyleSheet, Text, View, Button } from 'react-native';
✅ A. Inline Styling
Benefits of StyleSheet.create():
Better organization
Autocomplete and IntelliSense in VS Code
Potential for internal optimizations by React Native in the future
Easier maintenance — change in one place affects all uses
A subset of CSS
Use camelCase instead of kebab-case
Some CSS features are not available (like media queries, pseudo-selectors)
Examples:
CSS React Native
margin-left marginLeft
border ❌ (not supported)
borderWidth ✅
padding ✅
Supported Properties:
You’ll get:
Always check the React Native style documentation to confirm what’s supported.
Numeric values are automatically converted to density-independent pixels (dp) based on the
device.
Strings are required for things like color, fontWeight, etc.
💡 7. Structure of a StyleSheet
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
titleText: {
fontSize: 24,
fontWeight: 'bold',
color: 'blue',
marginBottom: 12,
},
});
We’re moving beyond just showing static text and buttons. The goal is to start building real layouts
— structured, interactive user interfaces.
The Example:
1. Input Section – Where the user types a goal and submits it.
2. List Section – Where the submitted goals will be shown.
a. Clean Slate
✅ Logical separation:
✅ Better control for styling and layout (e.g., spacing, sizing, flex).
<View style={styles.appContainer}>
<View>
<TextInput placeholder="Your course goal" />
<Button title="Add Goal" />
</View>
<View>
<Text>List of goals</Text>
</View>
</View>
🔹3. Components Used
Component Purpose
View Basic container block, similar to <div> in web
TextInput Input field for user to type a goal
Button Triggers action to add a goal
Text Displays static text (for now, “List of goals”)
All these are core components in React Native and must be imported from react-native.
When rendered, the app UI overlaps the status bar, making elements hard to see.
🛠️Temporary fix:
Apply a padding to the outermost View.
🧠 Pro Tip: Later, use components like SafeAreaView or libraries like expo-status-bar for better
handling.
Despite having padding, the layout looks cluttered and vertically stacked.
Issues:
React Native uses Flexbox by default to handle component layouts — just like in web development,
but with some differences.
✅ Why Flexbox?
🔍 What is Flexbox?
Flexbox (short for "Flexible Box Layout") is a layout system used to design responsive user interfaces.
React Native implements a subset of CSS Flexbox, allowing developers to:
📌 React Native’s Flexbox is almost the same as web CSS Flexbox, but tailored for mobile UI and
written in JavaScript object syntax.
Flexbox doesn’t apply to every element directly. It is applied to the parent container, which then
affects how its children (components) behave.
Example:
<View style={styles.container}>
<View style={styles.box1} />
<View style={styles.box2} />
<View style={styles.box3} />
</View>
Apply flexbox styles on styles.container to control how the boxes are arranged.
🔹 Axes in Flexbox
In React Native, the default flexDirection is "column" (unlike the web's default, which is "row").
🔹 flex: 1 Explained
When we write:
flex: 1
"Expand this component to fill all available space within its parent."
If multiple siblings have flex values, space is divided proportionally.
Example:
Value Description
flex-start All at the start
flex-end All at the end
center Centered
space-between Evenly spaced, first at start, last at end
space-around Equal space around each child
space-evenly Equal space between all children, including edges
Value Description
stretch (default) Stretch to fill container (if no size set)
flex-start Align to start of cross axis
flex-end Align to end
center Center on cross axis
<View style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<View style={{ width: 50, height: 50, backgroundColor: 'red' }} />
<View style={{ width: 50, height: 50, backgroundColor: 'blue' }} />
<View style={{ width: 50, height: 50, backgroundColor: 'green' }} />
</View>
It means:
We’re working with a View container that holds two child components:
Why?
Because the default value of flexDirection in React Native is 'column', meaning children are laid
out top-to-bottom.
To do that:
flexDirection: 'row'
🛠️Code Summary
<View style={styles.inputContainer}>
<TextInput style={styles.textInput} placeholder="Your goal" />
<Button title="Add Goal" />
</View>
inputContainer: {
flexDirection: 'row'
}
inputContainer: {
flexDirection: 'row',
justifyContent: 'space-between'
}
justifyContent Value Behavior
flex-start all items at start
center center items
space-between first and last at edges, rest evenly spaced
space-around equal space around items
space-evenly equal space between and around
In this case, since there are only two elements, space-between will push them to opposite ends.
🎨 Step 4: Styling the TextInput
Next, we make the input more readable and styled:
textInput: {
borderWidth: 1,
borderColor: '#cccccc', // light gray
width: '80%',
marginRight: 8,
padding: 8
}
🧩 Explanation:
📌 Note on Percentages
When setting a width using a percentage, wrap the value in quotes: '80%'
The percentage is relative to the parent container’s dimensions
However, since there is little or no margin between them, the layout looks tight — adding
marginRight helps visually.
✅ What is Flexbox?
Flexbox is a layout system designed for arranging items in a single dimension — either row-
wise (horizontally) or column-wise (vertically).
It helps you control:
o How children are placed inside a container (direction, spacing).
o How much space they take relative to one another.
o How they align with each other.
🧠 Key Differences: React Native vs Web Flexbox
Concept Web (CSS) React Native
Default layout model Not Flexbox Flexbox is default for all Views
Default direction row (horizontal) column (vertical)
Container type <div> <View>
⚠️Note: Even if you're not from a web development background, these differences help clarify why
things behave a certain way in React Native.
A parent <View> holds three colored boxes (red, blue, green) as child Views.
Each child has a <Text> element with numbers 1, 2, 3.
1. Axes
Examples:
3. justifyContent
Value Effect
'flex-start' Items are packed at the start of the main axis
'flex-end' Items at the end of the main axis
'center' Items centered on main axis
'space-between' Equal space between items
'space-around' Equal space around each item
'space-evenly' Equal space between + ends
4. alignItems
Value Effect
'stretch' Items stretch to fill cross axis (default)
'flex-start' Items align at start of cross axis
'flex-end' Items align at end of cross axis
'center' Items centered on cross axis
The flex property defines how much space an element should take relative to its siblings.
Total: 6 parts → Views take 1/6, 2/6, and 3/6 of the available space respectively.
Each child view only takes up space based on its own content (text, etc.).
If no width/height is defined, the box will shrink to fit.
When applied to the parent container, it limits the space that children can distribute.
This enables complex UIs like grids, headers, footers, and so on.
💡 Pro Tips
flex: 1 is extremely common. It's often used to make an element expand to fill remaining
space.
Nesting multiple Views with different flex settings lets you build responsive layouts.
Flexbox is always enabled in Views. You can’t disable it, but you control how it behaves.
Use padding, margin, borderWidth, and borderColor for visual fine-tuning.
App.js
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
<View
style={{
backgroundColor:'green',
// width:100,
// height:100,
flex:2,
justifyContent:'center',
alignItems:'center'
}}
>
<Text>2</Text>
</View>
<View
style={{
backgroundColor:'orange',
// width:100,
// height:100,
flex:1,
justifyContent:'center',
alignItems:'center'
}}
>
<Text>3</Text>
</View>
</View>
);
}
paddingTop: 50 → adds vertical space from the top of the screen, preventing the app from
touching the status bar.
paddingHorizontal: 16 → applies equal padding on the left and right side of the screen (16
pixels).
📌 Note: This is not directly related to Flexbox, but important for good layout design.
Problem:
Fix:
width: '70%'
Decreasing the TextInput’s width prevents it from hogging too much space, allowing room
for the button to fit.
📌 React Native uses percentage-based widths relative to the parent container, similar to CSS.
3. 📍 Centering Button Text Vertically
Problem:
The text in the Button is not vertically centered, as the button is stretched to match the height
of the TextInput.
Solution:
alignItems: 'center'
Add this to the parent View (input container) that holds both TextInput and Button.
alignItems aligns children along the cross-axis (vertical in this case, because the
flexDirection is row).
'center' makes sure the Button is vertically aligned with the TextInput.
4. ✏️Adding Space and Visual Separation Between Input and Goal List
paddingBottom: 24
borderBottomWidth: 1
borderBottomColor: '#ccc'
📌 These are purely styling enhancements, not Flexbox-driven, but part of building professional UIs.
Objective:
Add styles:
inputContainer: {
flex: 1, // Takes 1 portion
},
goalsContainer: {
flex: 3, // Takes 3 portions
},
Logic:
📌 These flex properties only work if both elements are siblings inside a common Flexbox parent.
Problem:
Even though child views use flex, it won’t work if the parent (AppContainer) doesn’t
occupy the full screen.
Fix:
appContainer: {
flex: 1
}
This ensures that the entire screen height is allocated to the appContainer.
Now, its children (inputContainer & goalsContainer) can split the available space
proportionally.
📌 Without flex: 1 on the outermost container, child flex values have no effect because there’s no
space to divide.
7. 🔄 Fine-Tuning Proportions and Spacing
Adjustments:
marginBottom creates spacing below the input area (outside spacing), which doesn’t interfere
with flex-based height distribution.
📌 This stage is about visual balance and might vary based on content or screen size.
💡 Expert-Level Tips
✅ Every View in React Native uses Flexbox by default — you don’t have to explicitly set
display: flex.
✅ If your layout isn’t reacting to flex values, check whether:
o Parent container has flex: 1
o All flex siblings are in the same level
🔄 Flex values (flex: 1, flex: 2, etc.) are relative. They do not mean "take up 1 row" —
they mean "share this ratio of space".
🧱 Nest views wisely. You can use nested Flexbox to create very complex and responsive
layouts.
EX:
App.js
--
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
🔹 Handling Events
In any app (web or mobile), you need to handle user interactions such as:
At this stage, we have a TextInput and a Button, but interacting with them does nothing because we
haven’t added event handlers yet.
Then, connect this function to the TextInput using the onChangeText prop:
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
So every keystroke updates the enteredGoalText variable, which we can use anywhere in the
component.
🔹 Step 3: Handle Button Press
Create another function that runs when the user presses the button:
function addGoalHandler() {
console.log(enteredGoalText);
}
Again, don’t call the function in JSX (addGoalHandler()) — just pass a reference
(addGoalHandler).
If we stored the input in a normal variable, the data would be lost on re-render.
useState ensures the data persists across re-renders and is reactive — it updates the UI or
triggers effects when changed.
But state, event handlers, component structure — all are exactly the same.
This confirms:
🔜 What’s Next?
Now that we:
App.js
--
import { useState } from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
console.log(enteredGoalText);
}
return (
<View style={styles.appContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.goalsContainer}>
<Text>List of goals...</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
appContainer: {
flex: 1,
paddingTop: 50,
paddingHorizontal: 16,
},
inputContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 24,
borderBottomWidth: 1,
borderBottomColor: '#cccccc',
},
textInput: {
borderWidth: 1,
borderColor: '#cccccc',
width: '70%',
marginRight: 8,
padding: 8,
},
goalsContainer: {
flex: 5,
},
});
📌 Section Goal: Managing A List Of Course Goals (in our Demo App)
We’re now focusing on managing and displaying a dynamic list of goals entered by the user.
This spreads the old array and adds the new goal.
It works, but may cause bugs if state updates are queued (happens often in React).
React may batch state updates, so relying on the current courseGoals directly might be
outdated.
Using a callback ensures you always operate on the most current and accurate state.
{courseGoals.map((goal) => (
<Text>{goal}</Text>
))}
✅ Example fix:
{courseGoals.map((goal) => (
<Text key={goal}>{goal}</Text>
))}
function App() {
const [enteredGoalText, setEnteredGoalText] = useState('');
const [courseGoals, setCourseGoals] = useState([]);
function goalInputHandler(text) {
setEnteredGoalText(text);
}
function addGoalHandler() {
setCourseGoals((currentGoals) => [...currentGoals, enteredGoalText]);
}
return (
<View>
<TextInput onChangeText={goalInputHandler} />
<Button title="Add Goal" onPress={addGoalHandler} />
{courseGoals.map((goal) => (
<Text key={goal}>{goal}</Text>
))}
</View>
);
}
✅ Best Practices
Always use functional updates (prevState => newState) when updating arrays or objects.
Never mutate state directly (e.g., never use array.push()).
Add key props to all dynamic lists.
Separate concerns: Keep input, update logic, and rendering clean and modular.
🧭 What’s Next?
Now that we:
EX:app.js
---
import { useState } from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
enteredGoalText,
]);
}
return (
<View style={styles.appContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.goalsContainer}>
{courseGoals.map((goal) => <Text key={goal}>{goal}</Text>)}
</View>
</View>
);
}
🎯 Problem:
You're rendering a list of goals dynamically using .map(), but the list items are plain text. We want
them to look styled and visually distinct.
🧱 Step-by-Step Explanation:
In React Native, styles are created using JavaScript objects, not CSS files.
⚠️Note: Unlike web development, this is not CSS, even if it looks like it. It’s JS-based styling.
✅ Solution:
Wrap Text inside a View (which does support borderRadius across platforms).
return (
<View style={styles.goalItem} key={goal}>
<Text>{goal}</Text>
</View>
);
3. Fixing Text Color
Once we moved the style to the View, the color: 'white' doesn’t affect the Text anymore.
Why?
In React Native, styles don’t cascade like in CSS. There is no inheritance between parent and child
components.
✅ Solution:
goalText: {
color: 'white',
}
<Text style={styles.goalText}>{goal}</Text>
Example:
/* Web CSS */
.parent {
color: white;
}
.child {
/* This would inherit white color */
}
But in React Native, Text won't inherit color from View. You must apply it directly.
3. Cross-Platform Differences
Issue Cause Fix
borderRadius missing iOS Text doesn't support it Use View wrapper
Styles not applied Styles don’t cascade in RN Apply directly on target components
goalItem: {
backgroundColor: '#5e0acc',
padding: 8,
margin: 8,
borderRadius: 6,
},
goalText: {
color: 'white',
}
✅ Result
With proper styling and correct component usage:
EX:app.js
--
import { useState } from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
enteredGoalText,
]);
}
return (
<View style={styles.appContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.goalsContainer}>
{courseGoals.map((goal) => (
<View key={goal} style={styles.goalItem}>
<Text style={styles.goalText}>{goal}</Text>
</View>
))}
</View>
</View>
);
}
⚠️Problem:
When adding many list items (e.g., goals), they eventually exceed the screen space. But surprisingly,
you can't scroll — even though that’s expected behavior on a webpage.
🧾 Explanation:
✅ But in React Native, scrolling is not automatic. You must explicitly use a scrollable container to
make it work.
➤ ScrollView
1. Import ScrollView
import { ScrollView } from 'react-native';
2. Wrap List in ScrollView
Replace the View that wraps the list of goals with ScrollView:
<ScrollView>
{goals.map((goal) => (
<View key={goal} style={styles.goalItem}>
<Text style={styles.goalText}>{goal}</Text>
</View>
))}
</ScrollView>
Now the list can be scrolled when its content exceeds screen height.
🧱 Layout Considerations
The proportions of the layout change — more space goes to input, less to the list.
🧾 Why?
The ScrollView does not automatically take full height unless bounded by a parent View.
It's the outer container (View) that controls layout size.
If you apply layout styles (like flex: 1) to the wrong component, proportions shift
unexpectedly.
<View style={styles.goalsContainer}>
<ScrollView>
{/* List Items Here */}
</ScrollView>
</View>
This setup:
Maintains layout proportions
Enables smooth scrolling
Explore them at React Native Docs, but here's a short list of important ones:
Result:
All items are rendered at once, which is inefficient for very large lists.
For large datasets, you'll learn to use FlatList (later in the course).
✅ Summary (TL;DR)
Concept Explanation
Scrolling not default React Native needs explicit scroll containers
Use ScrollView To make lists or content scrollable
Wrap in parent View Control layout size with outer View
iOS bounce effect Can be disabled via alwaysBounceVertical={false}
No scroll when list is short ScrollView needs overflowed content to show scroll behavior
Platform differences Props like bounces, alwaysBounceVertical vary per OS
EX:app.js
--
import { useState } from 'react';
import { StyleSheet, Text, View, Button, TextInput, ScrollView } from 'react-
native';
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
enteredGoalText,
]);
}
return (
<View style={styles.appContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.goalsContainer}>
<ScrollView>
{courseGoals.map((goal) => (
<View key={goal} style={styles.goalItem}>
<Text style={styles.goalText}>{goal}</Text>
</View>
))}
</ScrollView>
</View>
</View>
);
}
🔹 ScrollView Basics:
ScrollView is a component in React Native that makes content scrollable when it overflows
the screen.
Good for static or limited content, like a long article or form.
By default, it renders all children inside it, regardless of how many there are or whether
they're visible.
🔹 Performance Downside of ScrollView:
🔹 What is FlatList?
A React Native built-in component optimized for rendering large lists efficiently.
Automatically handles:
o Lazy rendering (only renders items when they come into view)
o Memory efficiency
o List virtualization (loads and removes items as needed)
Great for:
o Infinite scrolling
o Dynamically loaded lists (e.g. APIs)
o Any long list where performance matters
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return <Text>{itemData.item}</Text>;
}}
/>
Instead of storing strings in your list, store objects with key and text:
<Text>{itemData.item.text}</Text>
If your data has a custom ID field (like id), and not key, you can manually tell FlatList how to extract
it:
<FlatList
data={courseGoals}
keyExtractor={(item, index) => item.id}
renderItem={(itemData) => {
return <Text>{itemData.item.text}</Text>;
}}
/>
🔸 Lazy Loading:
✅ Final Thought
Always choose FlatList over ScrollView when:
You’re dealing with dynamic, growing, or large data sets.
Performance and memory usage are concerns.
You want built-in optimizations and simplicity.
EX: app.js
--
import { useState } from 'react';
import {
StyleSheet,
Text,
View,
Button,
TextInput,
ScrollView,
FlatList,
} from 'react-native';
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
}
return (
<View style={styles.appContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.goalsContainer}>
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return (
<View style={styles.goalItem}>
<Text style={styles.goalText}>{itemData.item.text}</Text>
</View>
);
}}
keyExtractor={(item, index) => {
return item.id;
}}
alwaysBounceVertical={false}
/>
</View>
</View>
);
}
📌 Problem
As apps grow, the main component file (like App.js) becomes large and harder to maintain. This
makes the code:
Difficult to read.
Hard to debug or enhance.
Less reusable.
✅ Solution
➕ Why?
Organizing your components into a components/ folder keeps your project structured and scalable.
🔧 Action
Create a folder:
/components
✅ Code Structure
function GoalItem() {
return (
// JSX for rendering a goal item
);
}
export default GoalItem;
✅ Note: You no longer need to import React from 'react' in newer React Native versions (React
17+).
🎯 Goal
Move the JSX code that renders a single item from App.js to GoalItem.js.
💡 Don’t move the list rendering logic (FlatList). Just move the part that renders one item.
🎨 Step 4: Move Styles with the Component
📌 Why?
Styles should be defined close to the component they belong to. This:
🔧 Implementation
Now the component and its styles live together in one place. ✅
📥 Import
import GoalItem from './components/GoalItem';
🧠 JSX Usage
Instead of returning the JSX directly inside FlatList.renderItem, return the custom component:
<FlatList
data={courseGoals}
renderItem={(itemData) => <GoalItem />}
keyExtractor={(item) => item.id}
/>
⚠️You will need to pass props like text, onDelete, etc. to GoalItem – not covered yet in the
transcript, but essential.
📦 Component Reusability
Splitting components makes them reusable in other parts of your app (or even other projects).
🧩 Component Encapsulation
Today, React Native apps are almost always built using Functional Components with Hooks like
useState, useEffect, etc.
💡 Problem: GoalItem needs to display a piece of data (like a goal text), but this data exists in App.js.
React Native, like React for the web, allows us to pass data from a parent component to a child
component via props.
🔧 Step-by-Step Breakdown
In GoalItem.js:
function GoalItem(props) {
return (
<View style={styles.goalItem}>
<Text style={styles.goalText}>{props.text}</Text>
</View>
);
}
props is an object that contains the data passed from the parent.
You can access props.text, props.id, or any other field you choose to send.
props.text holds the actual goal text (e.g., "Learn React Native").
<FlatList
data={courseGoals}
renderItem={(itemData) => (
<GoalItem text={itemData.item.text} />
)}
keyExtractor={(item) => item.id}
/>
React Native uses custom UI components (like <View>, <Text>) instead of HTML tags (<div>, <p>).
These components are not globally available, unlike HTML tags in React for the web.
In GoalItem.js, you must import each React Native UI component you use:
Without importing these, React Native won’t recognize View or Text, leading to a crash.
🧠 Expert-Level Concepts
🔁 Reusability
With props, you can reuse a component and pass different data to each instance.
<GoalItem text="Learn React Native" />
<GoalItem text="Practice UI layouts" />
EX:app.js
--
💡 Problem: GoalItem needs to display a piece of data (like a goal text), but this data exists in App.js.
✅ Solution: Use props
React Native, like React for the web, allows us to pass data from a parent component to a child
component via props.
🔧 Step-by-Step Breakdown
In GoalItem.js:
function GoalItem(props) {
return (
<View style={styles.goalItem}>
<Text style={styles.goalText}>{props.text}</Text>
</View>
);
}
props is an object that contains the data passed from the parent.
You can access props.text, props.id, or any other field you choose to send.
props.text holds the actual goal text (e.g., "Learn React Native").
<FlatList
data={courseGoals}
renderItem={(itemData) => (
<GoalItem text={itemData.item.text} />
)}
keyExtractor={(item) => item.id}
/>
React Native uses custom UI components (like <View>, <Text>) instead of HTML tags (<div>, <p>).
These components are not globally available, unlike HTML tags in React for the web.
In GoalItem.js, you must import each React Native UI component you use:
Without importing these, React Native won’t recognize View or Text, leading to a crash.
🧠 Expert-Level Concepts
🔁 Reusability
With props, you can reuse a component and pass different data to each instance.
🔨 GoalInput.js
function GoalInput(props) {
// Will add logic soon
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal"
onChangeText={goalInputHandler}
value={enteredGoalText} // For two-way binding
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
);
}
Moving all input-related JSX (View, TextInput, Button) from App.js to GoalInput.js.
Copying relevant styles (inputContainer, textInput) from App.js.
Creating a new component-specific state to manage input.
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
📤 The Problem
We want to pass the goal text from GoalInput to App, but the state (courseGoals) and its update
logic (addGoalHandler) live in App.
In App.js:
function addGoalHandler() {
props.onAddGoal(enteredGoalText); // pass data to parent
setEnteredGoalText(''); // clear input
}
setEnteredGoalText('');
But this was not reflected in the UI because the TextInput did not know it was being reset.
✅ Result
Modular, reusable GoalInput component.
Clear data flow: input (child) ➝ list (parent).
Reset input after each submission.
Two-way binding for accurate UI updates.
EX:app.js
--
import { useState } from 'react';
import { StyleSheet, View, FlatList } from 'react-native';
function addGoalHandler(enteredGoalText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
}
return (
<View style={styles.appContainer}>
<GoalInput onAddGoal={addGoalHandler} />
<View style={styles.goalsContainer}>
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return <GoalItem text={itemData.item.text} />;
}}
keyExtractor={(item, index) => {
return item.id;
}}
alwaysBounceVertical={false}
/>
</View>
</View>
);
}
Components/GoalItem.js
---
import { StyleSheet, View, Text } from 'react-native';
function GoalItem(props) {
return (
<View style={styles.goalItem}>
<Text style={styles.goalText}>{props.text}</Text>
</View>
);
}
Components/Goalnput.js
---
import { useState } from 'react';
import { View, TextInput, Button, StyleSheet } from 'react-native';
function GoalInput(props) {
const [enteredGoalText, setEnteredGoalText] = useState('');
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
props.onAddGoal(enteredGoalText);
setEnteredGoalText('');
}
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
value={enteredGoalText}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
);
}
🎯 Goal
Allow users to tap on a list item to delete it from the list.
👉 Web Example:
<div onClick={deleteItem}>Delete</div>
File: GoalItem.js
function GoalItem(props) {
return (
<Pressable onPress={props.onDeleteItem}>
<View style={styles.goalItem}>
<Text>{props.text}</Text>
</View>
</Pressable>
);
}
Key Concept:
The onPress function will run whenever the user taps that item.
Since the list is managed in App.js, the deletion logic must also live there.
File: App.js
function deleteGoalHandler() {
console.log("DELETE");
}
📝 For now, this is just a console log, but later we’ll update the actual list state using setCourseGoals.
🧩 Passing Down the Handler via Props
React uses one-way data flow, so the child (GoalItem) must receive the function from the parent
(App.js) through props.
🔄 Flow Diagram
User taps GoalItem
↓
GoalItem runs props.onDeleteItem()
↓
App.js receives the call in deleteGoalHandler()
↓
Logs "DELETE" to console (for now)
This will be improved later using Pressable's pressed state or animation techniques.
🧠 Expert Concept Review
Concept Explanation
Component Touch interactions must be wrapped using a React Native interactive
Wrapping component (Pressable)
Event Handling Unlike the web, onClick doesn't exist. Use onPress.
State (like goal list) must be managed in the top-level component that controls
Lifting State
the data (App.js)
Functional Props Functions passed as props let children notify parents of user interactions
🧰 What’s Ready
✅ Items are tappable
✅ Tapping triggers a function
✅ Communication from child to parent is set up
✅ Scaffold for deletion is in place
🔄 Objective
You already built a tappable list using Pressable. Now, you’ll make those taps actually delete items
from the list.
To achieve this:
🧩 Step-by-Step Breakdown
🔧 Step 1: Handle Deletion by ID
function deleteGoalHandler() {
console.log("DELETE");
}
Every item in your list should have a unique identifier (usually assigned with uuid or
Math.random() during creation). This ID:
In React (and React Native), if you’re updating state based on the previous state, always use the
functional form of setState.
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id !== id);
});
🚀 filter() Recap
Logic Used:
goal.id !== id
Problem:
🔍 Explanation:
✅ When the item is pressed, deleteGoalHandler(id) is triggered — with the correct ID!
🧠 Alternative to .bind()
Instead of using .bind(), you could also use an inline arrow function:
This is more readable and preferred in modern React unless performance is a major concern (e.g., in
large lists).
🔗 Step 3: Ensure id is Actually Passed Down from App.js
Rendering:
<GoalItem
text={itemData.item.text}
id={itemData.item.id}
onDeleteItem={deleteGoalHandler}
/>
GoalItem.js
<Pressable onPress={props.onDeleteItem.bind(this, props.id)}>
<View>
<Text>{props.text}</Text>
</View>
</Pressable>
🎯 Final Result
You can now tap to delete any list item.
Items are filtered out by their unique id.
The list re-renders with the item removed.
Everything is handled with clean one-way data flow and functional state updates.
💡 Pro Tips
Best Practice Why It Matters
Use unique IDs Prevent accidental deletion of duplicates
Use functional setState Ensures you always work with the latest state
Use .filter() Clean and efficient array manipulation
Always return new arrays (immutability = performance +
Avoid mutating state
reliability)
Use arrow functions instead of .bind()
Shorter and cleaner syntax
(optional)
EX:app.js
--
import { useState } from 'react';
import { StyleSheet, View, FlatList } from 'react-native';
function addGoalHandler(enteredGoalText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
}
function deleteGoalHandler(id) {
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id !== id);
});
}
return (
<View style={styles.appContainer}>
<GoalInput onAddGoal={addGoalHandler} />
<View style={styles.goalsContainer}>
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return (
<GoalItem
text={itemData.item.text}
id={itemData.item.id}
onDeleteItem={deleteGoalHandler}
/>
);
}}
keyExtractor={(item, index) => {
return item.id;
}}
alwaysBounceVertical={false}
/>
</View>
</View>
);
}
Components/GoalInput.js
--
import { useState } from 'react';
import { View, TextInput, Button, StyleSheet } from 'react-native';
function GoalInput(props) {
const [enteredGoalText, setEnteredGoalText] = useState('');
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
props.onAddGoal(enteredGoalText);
setEnteredGoalText('');
}
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
value={enteredGoalText}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
);
}
function GoalItem(props) {
return (
<Pressable onPress={props.onDeleteItem.bind(this, props.id)}>
<View style={styles.goalItem}>
<Text style={styles.goalText}>{props.text}</Text>
</View>
</Pressable>
);
}
🔍 Problem:
After implementing item deletion via Pressable, there is no visual feedback when a user taps on an
item. That feels unresponsive, especially on mobile apps.
✅ Goal:
Give press feedback for both Android and iOS platforms using platform-specific and cross-platform
techniques.
🧱 Background:
React Native’s Pressable component provides:
🔧 Step-by-Step Breakdown
The ripple appears when the item is tapped and held briefly.
You can use hex colors (#dddddd is a light gray ripple).
The ripple is only visible on Android.
It does not show on iOS.
But if you want the ripple to only appear within the text area, you can wrap the Text only:
<View style={styles.goalItem}>
<Pressable android_ripple={{ color: '#dddddd' }}>
<Text>{props.text}</Text>
</Pressable>
</View>
However, this might make the touch target smaller, so it's best to have Pressable around the full
item for better usability.
If the ripple only appears around the text and not the padding, it's likely due to padding being on the
outer View.
Solution:
Move the padding into the Text or adjust the layout like this:
Now, the ripple respects the padding and gives better visual feedback.
🔧 Define Styles:
const styles = StyleSheet.create({
goalItem: {
margin: 8,
padding: 8,
backgroundColor: '#5e0acc',
borderRadius: 6,
},
pressedItem: {
opacity: 0.5,
},
});
🧠 Expert Tips
Topic Tip
android_ripple Use for Android-only effects. Configure color and size using the style.
style={({ pressed }) Works on both platforms. Very flexible: change background, borders,
=> ...} shadows, etc.
Always add visual feedback for touchable elements — it's a core UX
UI Feedback
principle in mobile apps.
Prefer Pressable wrapping the full item for better accessibility and a
Component Structure
larger hit area.
Combine press feedback with animations (like using Animated.View)
Advanced Styling
for smoother UX.
<Pressable
android_ripple={{ color: '#dddddd' }}
style={({ pressed }) => pressed && styles.pressedItem}
>
<View style={styles.goalItem}>
<Text>{props.text}</Text>
</View>
</Pressable>
🧪 Result
✅ Smooth, consistent press feedback on both iOS and Android.
🧭 What’s Next?
Now that your app handles:
Adding goals ✅
Deleting goals ✅
Visual press feedback ✅
EX:app.js
--
import { useState } from 'react';
import { StyleSheet, View, FlatList } from 'react-native';
function addGoalHandler(enteredGoalText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
}
function deleteGoalHandler(id) {
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id !== id);
});
}
return (
<View style={styles.appContainer}>
<GoalInput onAddGoal={addGoalHandler} />
<View style={styles.goalsContainer}>
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return (
<GoalItem
text={itemData.item.text}
id={itemData.item.id}
onDeleteItem={deleteGoalHandler}
/>
);
}}
keyExtractor={(item, index) => {
return item.id;
}}
alwaysBounceVertical={false}
/>
</View>
</View>
);
}
Components/GoalInput.js
--
import { useState } from 'react';
import { View, TextInput, Button, StyleSheet } from 'react-native';
function GoalInput(props) {
const [enteredGoalText, setEnteredGoalText] = useState('');
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
props.onAddGoal(enteredGoalText);
setEnteredGoalText('');
}
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
value={enteredGoalText}
/>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
);
}
export default GoalInput;
Components/GoalItem.js
--
import { StyleSheet, View, Text, Pressable } from 'react-native';
function GoalItem(props) {
return (
<View style={styles.goalItem}>
<Pressable
android_ripple={{ color: '#210644' }}
onPress={props.onDeleteItem.bind(this, props.id)}
style={({ pressed }) => pressed && styles.pressedItem}
>
<Text style={styles.goalText}>{props.text}</Text>
</Pressable>
</View>
);
}
🎯 Goal:
Improve the app's look and user experience by:
🧠 What is a Modal?
A Modal in React Native:
Is a full-screen overlay.
Displays temporary content like forms or confirmation dialogs.
Blocks interaction with the underlying screen until dismissed.
Comes from the built-in React Native component: Modal.
🧱 Step-by-Step Breakdown
✅ 1. Create a Modal with Input Fields
Wrap the input area (View containing TextInput and Button) with a <Modal> component:
<Modal>
<View>
<TextInput />
<Button title="Add Goal" />
</View>
</Modal>
🔍 Problem: The modal always shows by default, covering the screen and hiding other content.
In App.js:
function startAddGoalHandler() {
setModalIsVisible(true);
}
<Button
title="Add New Goal"
color="#5e0acc"
onPress={startAddGoalHandler}
/>
🔍 But this approach hides/shows the entire component, without using the Modal's built-in animation
system.
✅ 3. Use the Modal's visible and animationType Props
Instead of conditionally rendering the component, always mount the modal, and control its visibility
using props:
🧩 GoalInput.js:
<Modal visible={props.visible} animationType="slide">
<View>
<TextInput />
<Button title="Add Goal" />
</View>
</Modal>
🧠 Best Practice:
This creates a controlled component where the parent manages whether the modal is open.
✨ Result So Far:
The modal slides up when the button is pressed.
Input UI appears within a native-feeling modal.
Reusable, cleanly separated input logic inside GoalInput.
🚫 Still Missing:
Close button to dismiss the modal.
Styling to center the input fields inside the modal.
Optional: Prevent accidental closing via hardware back button.
✅ Additional Improvements (Next Steps)
In GoalInput, create a function passed from the parent to hide the modal:
function endAddGoalHandler() {
setModalIsVisible(false);
}
<GoalInput
visible={modalIsVisible}
onCancel={endAddGoalHandler}
/>
Since the modal fills the entire screen, we need to center the form:
<View style={styles.inputContainer}>
<TextInput ... />
<View style={styles.buttonContainer}>
<Button title="Add Goal" />
<Button title="Cancel" />
</View>
</View>
Example styles:
inputContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 16,
backgroundColor: '#311b6b',
},
buttonContainer: {
flexDirection: 'row',
marginTop: 16,
gap: 8,
}
Use flex: 1 on the outer container of the modal to take up the full screen.
💡 Expert Insights
Use controlled components: the modal's visibility should be controlled by React state.
Avoid hiding/unmounting modals directly with {modalIsVisible && <Modal />}; prefer
controlling visible.
Modal is especially useful for:
o Form input
o Confirmation dialogs
o Temporary actions (filters, prompts, etc.)
For more control, create custom animations with Animated API or libraries like react-
native-reanimated.
🧩 Structure Overview
In the GoalInput.js component:
<Modal>
<View style={styles.inputContainer}>
<TextInput />
<View style={styles.buttonContainer}>
<View style={styles.button}><Button title="Add Goal" /></View>
<View style={styles.button}><Button title="Cancel" /></View>
</View>
</View>
</Modal>
This structure helps organize layout and enables style control over otherwise limited components like
<Button>.
✅ Step-by-Step Breakdown
Place both "Add Goal" and "Cancel" buttons below the TextInput, inside a horizontal View.
<View style={styles.buttonContainer}>
<View style={styles.button}>
<Button title="Add Goal" />
</View>
<View style={styles.button}>
<Button title="Cancel" />
</View>
</View>
🔍 Why wrap buttons in Views?
Because React Native’s built-in <Button> doesn’t support style. Wrapping them in View lets you:
Set width
Add spacing (margins)
Align them horizontally
Control layout using Flexbox
inputContainer: {
flex: 1, // Occupy full screen height
justifyContent: 'center', // Vertically center content
alignItems: 'center', // Horizontally center content
padding: 16, // Adds breathing room around content
backgroundColor: '#311b6b', // Optional: background color for modal
}
💡 You could also use % width (like 30–40%), but fixed width provides more control and consistency
across screen sizes.
✅ TextInput Styling
textInput: {
borderWidth: 1,
borderColor: '#e4d0ff',
backgroundColor: '#e4d0ff',
color: '#120438',
borderRadius: 6,
width: '100%', // Full width of parent container
padding: 8,
}
🧠 Design Considerations
Element Style Feature Purpose
flex: 1 Expand modal height Ensure the modal occupies full screen
Make the modal content appear in the center of
justifyContent: 'center' Center vertically
the screen
alignItems: 'center' Center horizontally Line up all input/buttons properly
marginTop on Space between
Prevent crowding
buttonContainer elements
padding on root container Overall spacing Prevent content from touching screen edges
Since Button doesn't support custom
Wrapping buttons in Views Styling workaround
width/margin directly
For Add Goal, you may also want to hide the modal after adding the goal:
<Button
title="Add Goal"
onPress={() => {
props.onAddGoal(enteredGoalText); // Add goal
props.onCancel(); // Close modal
}}
/>
Ex:app.js
--
import { useState } from 'react';
import { StyleSheet, View, FlatList, Button } from 'react-native';
function startAddGoalHandler() {
setModalIsVisible(true);
}
function addGoalHandler(enteredGoalText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
}
function deleteGoalHandler(id) {
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id !== id);
});
}
return (
<View style={styles.appContainer}>
<Button
title="Add New Goal"
color="#5e0acc"
onPress={startAddGoalHandler}
/>
<GoalInput visible={modalIsVisible} onAddGoal={addGoalHandler} />
<View style={styles.goalsContainer}>
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return (
<GoalItem
text={itemData.item.text}
id={itemData.item.id}
onDeleteItem={deleteGoalHandler}
/>
);
}}
keyExtractor={(item, index) => {
return item.id;
}}
alwaysBounceVertical={false}
/>
</View>
</View>
);
}
Components/GoalInput.js
--
import { useState } from 'react';
import { View, TextInput, Button, StyleSheet, Modal } from 'react-native';
function GoalInput(props) {
const [enteredGoalText, setEnteredGoalText] = useState('');
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
props.onAddGoal(enteredGoalText);
setEnteredGoalText('');
}
return (
<Modal visible={props.visible} animationType="slide">
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
value={enteredGoalText}
/>
<View style={styles.buttonContainer}>
<View style={styles.button}>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.button}>
<Button title="Cancel" />
</View>
</View>
</View>
</Modal>
);
}
📌 Objective:
Implement logic so that the modal closes when either:
🧠 Key Principle:
🛠 Implementation: Step-by-Step
function endAddGoalHandler() {
setModalIsVisible(false); // 🔴 This will hide the modal
}
We are now "lifting state up" and passing down control functions via props.
✅ Step 3: Use onCancel Prop in GoalInput for Cancel Button
In GoalInput.js:
We reuse the same endAddGoalHandler for both cases to avoid repeating code.
💡 Why It Works
React and React Native rely on unidirectional data flow:
So, any change in UI (like closing a modal) needs to be handled upstream in the parent component.
✅ Final Summary
Component Responsibility
App.js Manages modal state (modalIsVisible)
GoalInput.js Shows modal UI and handles local input
endAddGoalHandler Closes modal – called from both buttons
onAddGoal Adds the goal and calls onCancel() to close modal
🧠 Expert Tips
🔁 Reuse functions like endAddGoalHandler across events (DRY principle).
🎛 Always control modal visibility from one place (usually parent).
🎯 Keep components dumb where possible (GoalInput shouldn't hold modal state).
EX:app.js
--
import { useState } from 'react';
import { StyleSheet, View, FlatList, Button } from 'react-native';
function startAddGoalHandler() {
setModalIsVisible(true);
}
function endAddGoalHandler() {
setModalIsVisible(false);
}
function addGoalHandler(enteredGoalText) {
setCourseGoals((currentCourseGoals) => [
...currentCourseGoals,
{ text: enteredGoalText, id: Math.random().toString() },
]);
endAddGoalHandler();
}
function deleteGoalHandler(id) {
setCourseGoals((currentCourseGoals) => {
return currentCourseGoals.filter((goal) => goal.id !== id);
});
}
return (
<View style={styles.appContainer}>
<Button
title="Add New Goal"
color="#5e0acc"
onPress={startAddGoalHandler}
/>
<GoalInput
visible={modalIsVisible}
onAddGoal={addGoalHandler}
onCancel={endAddGoalHandler}
/>
<View style={styles.goalsContainer}>
<FlatList
data={courseGoals}
renderItem={(itemData) => {
return (
<GoalItem
text={itemData.item.text}
id={itemData.item.id}
onDeleteItem={deleteGoalHandler}
/>
);
}}
keyExtractor={(item, index) => {
return item.id;
}}
alwaysBounceVertical={false}
/>
</View>
</View>
);
}
Components/GoalInput.js
--
import { useState } from 'react';
import { View, TextInput, Button, StyleSheet, Modal } from 'react-native';
function GoalInput(props) {
const [enteredGoalText, setEnteredGoalText] = useState('');
function goalInputHandler(enteredText) {
setEnteredGoalText(enteredText);
}
function addGoalHandler() {
props.onAddGoal(enteredGoalText);
setEnteredGoalText('');
}
return (
<Modal visible={props.visible} animationType="slide">
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
placeholder="Your course goal!"
onChangeText={goalInputHandler}
value={enteredGoalText}
/>
<View style={styles.buttonContainer}>
<View style={styles.button}>
<Button title="Add Goal" onPress={addGoalHandler} />
</View>
<View style={styles.button}>
<Button title="Cancel" onPress={props.onCancel} />
</View>
</View>
</View>
</Modal>
);
}
export default GoalInput;
Components/GoalInput.js
--
import { StyleSheet, View, Text, Pressable } from 'react-native';
function GoalItem(props) {
return (
<View style={styles.goalItem}>
<Pressable
android_ripple={{ color: '#210644' }}
onPress={props.onDeleteItem.bind(this, props.id)}
style={({ pressed }) => pressed && styles.pressedItem}
>
<Text style={styles.goalText}>{props.text}</Text>
</Pressable>
</View>
);
}
📌 Objective:
Enhance the visual design of the app by:
React Native provides an <Image /> component (like <img> in HTML) to display both:
project-root/
│
├── assets/
│ └── images/
│ └── goal.png ← Add your image here
React Native requires local image assets to be imported using require() (not just string paths like
HTML).
In your GoalInput.js:
image: {
width: 100,
height: 100,
margin: 20,
},
Initially, you might not see the image because the image background and modal background are
both white.
inputContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 16,
backgroundColor: '#311b6b', // Dark purple
},
textInput: {
borderWidth: 1,
borderColor: '#cccccc',
backgroundColor: '#e4d0ff',
color: '#120438', // dark text on light background
padding: 8,
width: '100%',
},