Topic 5: State and Props
📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: json-data, state-and-props
Why this matters
Up until now, your components have been static — they show the same thing every time. But real apps change. A button count goes up when you tap it. A form fills in as you type. A list updates when data arrives. That's state — a component's own private memory. And when one component needs to share information with another, it passes props — like handing a note to a child. In React Native, these two mechanisms are everything. Once you understand how state and props work together, you'll finally see how dynamic apps are actually built.
What You'll Learn
- Use the
useStatehook to track and update changing data inside a component - Pass data between components using props, keeping child components reusable
- Lift state to a parent component and distribute it as props to children
- Pass callback functions as props so children can trigger parent logic
- Manage complex object-shaped state with functional updater patterns
The Analogy
Think of a Vizag dispatch tower and its field scouts. The tower keeps a private logbook (state) — the current headcount, mission names, alert levels — that only the tower itself can update. When a scout needs information, the tower radios it down (props); the scout can read the message but cannot rewrite the logbook directly. If a scout discovers something new and needs the logbook changed, she calls back to the tower using a pre-agreed radio code (a function passed as a prop) and the tower updates its own log. The tower re-broadcasts the new state to all scouts automatically. Nothing leaks, nothing breaks, and every outpost stays in sync.
Chapter 1: State — A Component's Private Logbook
State is a built-in mechanism that lets a component remember changing data between renders. When state changes, React Native re-renders the component to reflect those changes.
The useState hook returns a pair: the current value and a setter function. Call the setter and the component re-renders with the new value.
CounterComponent.js — a minimal example tracking a numeric count:
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const CounterComponent = () => {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.text}>Count: {count}</Text>
<Button title="Increment" onPress={() => setCount(count + 1)} />
<Button title="Decrement" onPress={() => setCount(count - 1)} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#fff',
borderRadius: 8,
alignItems: 'center',
},
text: {
fontSize: 24,
marginBottom: 10,
},
});
export default CounterComponent;
Key points:
useState(0)initializescountto0setCount(count + 1)triggers a re-render with the updated value- The setter is always called, never the state variable itself
Chapter 2: Props — Radio Signals Between Components
Props (short for properties) are read-only values passed from a parent component down to a child. They make components reusable: the same component can render different content depending on what it receives.
GreetingComponent.js — displays a personalized message based on the name prop:
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const GreetingComponent = ({ name }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>Hello, {name}!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#e0e0e0',
borderRadius: 8,
},
text: {
fontSize: 18,
color: '#555',
},
});
export default GreetingComponent;
Props rules:
- A child component cannot modify its own props
- The parent decides what value is passed; the child only reads it
- Props can be strings, numbers, booleans, objects, arrays, or functions
Chapter 3: Combining State and Props — The Parent-Child Pattern
The most common React Native pattern is a parent that owns state and passes slices of it down as props to children. When the parent's state changes, every child that receives it as a prop automatically re-renders.
Step 1: Create the Parent Component
ParentComponent.js — manages a name string in state and distributes it:
import React, { useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import CounterComponent from './CounterComponent';
import GreetingComponent from './GreetingComponent';
const ParentComponent = () => {
const [name, setName] = useState('Vizag Developer');
return (
<View style={styles.container}>
<GreetingComponent name={name} />
<Button title="Change Name" onPress={() => setName('React Native Developer')} />
<CounterComponent />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
});
export default ParentComponent;
GreetingComponent receives name as a prop. When the button fires setName, the parent re-renders and the child immediately shows the new greeting.
Step 2: Integrate the Parent Component in App
App.js — mounts ParentComponent inside a ScrollView:
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import ParentComponent from './ParentComponent';
export default function App() {
return (
<ScrollView contentContainerStyle={styles.container}>
<ParentComponent />
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
});
Chapter 4: Advanced Usage — Functions as Props and Complex State
Passing Functions as Props
A parent can hand a child a callback function via props. The child calls it in response to user interaction — without needing to know what it does. This keeps logic centralized in the parent while letting the child trigger it.
ChildComponent.js — receives an onButtonPress handler and wires it to a button:
import React from 'react';
import { View, Button, StyleSheet } from 'react-native';
const ChildComponent = ({ onButtonPress }) => {
return (
<View style={styles.container}>
<Button title="Press Me" onPress={onButtonPress} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#d0d0d0',
borderRadius: 8,
},
});
export default ChildComponent;
The child doesn't know what onButtonPress does — it just calls it. The parent defines the behavior.
Managing Complex State (Object Shape)
When state is an object with multiple properties, use the functional updater form of the setter — setUser(prev => ...) — and spread the previous value so only the changed key is overwritten.
ComplexStateComponent.js — tracks a user object with name and age:
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const ComplexStateComponent = () => {
const [user, setUser] = useState({ name: 'John', age: 30 });
const increaseAge = () => {
setUser((prevUser) => ({ ...prevUser, age: prevUser.age + 1 }));
};
return (
<View style={styles.container}>
<Text style={styles.text}>Name: {user.name}</Text>
<Text style={styles.text}>Age: {user.age}</Text>
<Button title="Increase Age" onPress={increaseAge} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 20,
backgroundColor: '#fff',
borderRadius: 8,
alignItems: 'center',
},
text: {
fontSize: 18,
marginBottom: 10,
},
});
export default ComplexStateComponent;
The { ...prevUser, age: prevUser.age + 1 } pattern copies all existing keys and overrides only age. Without the spread, you'd wipe out name.
Chapter 5: Combining All Components in the App
The class wired every component into a single App.js to demonstrate state, props, function-as-prop, and complex state working side by side.
App.js — final composition:
import React from 'react';
import { ScrollView, StyleSheet, View } from 'react-native';
import ParentComponent from './ParentComponent';
import ChildComponent from './ChildComponent';
import ComplexStateComponent from './ComplexStateComponent';
export default function App() {
const handleButtonPress = () => {
alert('Button pressed in Child Component!');
};
return (
<ScrollView contentContainerStyle={styles.container}>
<ParentComponent />
<ChildComponent onButtonPress={handleButtonPress} />
<ComplexStateComponent />
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 16,
},
});
handleButtonPress is defined in App, passed to ChildComponent via onButtonPress, and called when the child's button fires — classic upward communication through a function prop.
graph TD
App -->|onButtonPress callback| ChildComponent
App --> ParentComponent
App --> ComplexStateComponent
ParentComponent -->|name prop| GreetingComponent
ParentComponent --> CounterComponent
GreetingComponent -->|renders| HelloText["Hello, {name}!"]
CounterComponent -->|useState| CountState["count: 0"]
ComplexStateComponent -->|useState| UserState["{ name, age }"]
🧪 Try It Yourself
Task: Build a ProfileCard component that holds a { username, score } state object. Render both fields. Add two buttons: "Add Point" (increments score by 1) and "Reset" (sets score back to 0). Then mount ProfileCard inside a ParentComponent that also passes a greeting prop (e.g., "Welcome back") for ProfileCard to display above the username.
Success criterion: Tapping "Add Point" increases the displayed score. Tapping "Reset" brings it back to 0. The greeting text comes from the parent, not from the card's own state.
Starter snippet:
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
const ProfileCard = ({ greeting }) => {
const [profile, setProfile] = useState({ username: 'scout_dev', score: 0 });
const addPoint = () => {
setProfile((prev) => ({ ...prev, score: prev.score + 1 }));
};
const reset = () => {
setProfile((prev) => ({ ...prev, score: 0 }));
};
return (
<View style={styles.card}>
<Text>{greeting}</Text>
<Text>Username: {profile.username}</Text>
<Text>Score: {profile.score}</Text>
<Button title="Add Point" onPress={addPoint} />
<Button title="Reset" onPress={reset} />
</View>
);
};
const styles = StyleSheet.create({
card: { padding: 20, backgroundColor: '#eef', borderRadius: 8 },
});
export default ProfileCard;
🔍 Checkpoint Quiz
Q1. What is the fundamental difference between state and props in React Native?
A) State is for numbers; props are for strings B) State is owned and managed by the component itself; props are passed in from outside and are read-only C) Props trigger re-renders; state does not D) They are interchangeable — either can be used for any data
Q2. Given this snippet, what does the component display after the button is pressed once?
const [count, setCount] = useState(5);
// ...
<Text>Count: {count}</Text>
<Button title="Go" onPress={() => setCount(count - 1)} />
A) Count: 5 B) Count: 6 C) Count: 4 D) Count: 0
Q3. What is the bug in the following complex state update?
const [user, setUser] = useState({ name: 'Alice', age: 25 });
const birthday = () => setUser({ age: user.age + 1 });
A) useState cannot hold objects
B) The setter is called without the functional updater pattern
C) The spread is missing — name will be lost after the update
D) age cannot be incremented on an object
Q4. You have a TodoList parent that owns a todos array in state. A child TodoItem needs to mark an item complete. How should this be implemented?
A) Give TodoItem its own copy of todos in local state
B) Pass a markComplete callback function as a prop from TodoList to TodoItem; the child calls it with the item id
C) Have TodoItem directly mutate the array prop it receives
D) Use a global variable so both components can read and write it
A1. B — State lives inside the component and can only be updated by that component via its setter. Props are passed from a parent and are immutable from the child's perspective.
A2. C — useState(5) starts at 5; pressing the button calls setCount(5 - 1), so the display updates to Count: 4.
A3. C — setUser({ age: user.age + 1 }) replaces the entire state object with { age: 26 }, dropping the name key entirely. The correct form is setUser(prev => ({ ...prev, age: prev.age + 1 })).
A4. B — This is the standard "lift state up / pass callbacks down" pattern. The parent owns the data and the mutation logic; the child just signals intent by calling the provided function.
🪞 Recap
useStategives a component private memory that, when updated, triggers a re-render- Props flow one way — parent to child — and are always read-only from the child's perspective
- A parent component can own state and pass it as props, keeping children lightweight and reusable
- Functions passed as props let children communicate upward without coupling to parent internals
- Complex object state should use the functional updater
prev => ({ ...prev, changed: newVal })to avoid accidental key loss
📚 Further Reading
- React Native — State — official guide to managing state in React Native components
- React Docs — useState Hook — complete API reference with edge cases and functional updater patterns
- React Docs — Passing Props to a Component — the canonical explanation of how props work
- ⬅️ Previous: JSON Data
- ➡️ Next: Basics: Styles, Components, Text Inputs, Buttons, ScrollView, Activity Indicator, Images, Modals