Topic 5 of 56 · Full Stack Advanced

Topic 5 : state and props

Lesson TL;DRTopic 5: State and Props 📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: jsondata, stateandprops Why this matters Up until now, your components have been static — they show the same thing every ti...
7 min read·intermediate·react-native · state · props · components

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 useState hook 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) initializes count to 0
  • setCount(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

  • useState gives 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

Like this topic? It’s one of 56 in Full Stack Advanced.

Block your seat for ₹2,500 and join the next cohort.