Learn React Native - State, Props & Hooks
Episode 5 of 23

Learn React Native - State, Props & Hooks

This episode dissects state, props, and hooks in React Native: useState, useEffect, useRef, useMemo, and useCallback, component composition patterns, and typing props with TypeScript so the code is safe from type errors.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

In episode 4 you learned to build static screens. Now those screens need to come alive: responding to button taps, loading data from the server, and storing input values. All of that is controlled by state, props, and hooks — the same concepts as React on the web, but with a mobile flavor. Episode 5 dissects the most frequently used hooks — useState, useEffect, useRef, useMemo, and useCallback — then how to compose components with props and composition, as well as typing props with TypeScript. This is the heart of your app's interactive logic.

State with useState

Building a Simple Counter

useState stores a value that can change and triggers a re-render when it changes:

JSCounter with useState
import React, { useState } from "react";
import { View, Text, Pressable, StyleSheet } from "react-native";
 
export function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <View style={styles.box}>
      <Text style={styles.label}>{count}</Text>
      <Pressable style={styles.button} onPress={() => setCount(count + 1)}>
        <Text>Tambah</Text>
      </Pressable>
    </View>
  );
}

Notice the pattern in Counter: useState(0) initializes the state with 0, and setCount(count + 1) updates the value. Every setCount call triggers a re-render with the new value.

State on a TextInput

A very common mobile pattern is binding a TextInput to state:

JSControlled TextInput
const [nama, setNama] = useState("");
 
<TextInput
  value={nama}
  onChangeText={setNama}
  placeholder="Masukkan nama"
  style={styles.input}
/>

value={nama} and onChangeText={setNama} make this input fully controlled — the value is always in sync with the nama state. This pattern is the foundation for forms, search, and chat.

Side Effects with useEffect

When useEffect Is Needed

useEffect handles side effects: fetching data, subscribing to events, or changing things outside of rendering. On mobile, component lifecycle follows the same mounting and unmounting as React on the web:

JSuseEffect for fetching data
import React, { useState, useEffect } from "react";
import { View, Text } from "react-native";
 
export function UserStatus() {
  const [status, setStatus] = useState("memuat...");
 
  useEffect(() => {
    let aktif = true;
    fetch("https://api.example.com/status")
      .then((res) => res.json())
      .then((data) => {
        if (aktif) setStatus(data.status);
      });
    return () => {
      aktif = false;
    };
  }, []);
 
  return (
    <View>
      <Text>Status: {status}</Text>
    </View>
  );
}

The UserStatus pattern above shows two important habits: the empty dependency array [] means the effect only runs once after mount, and the cleanup function aktif = false prevents state updates after the component unmounts — preventing a common error in React Native.

Effects That Depend on Props

If an effect uses values from props or state, put those values in the dependency array. For example, useEffect(..., [userId]) will re-run the fetch every time userId changes — a basic pattern for detail screens.

Holding Stable Values with useRef

useRef stores values that don't trigger a re-render when changed, and the value persists for the lifetime of the component. Two main use cases:

  • Holding a reference to a native component, for example a ScrollView for programmatic scrolling.
  • Holding a mutable value that changes frequently without needing a re-render, such as a timer or interval id.

Example of the second use case: timerRef.current stores the interval id set by setInterval and cleared by clearInterval. Since it doesn't trigger a render, storing it in useRef is much cleaner than in useState.

Optimizing with Memo and Callback

useMemo for Expensive Values

useMemo caches the result of an expensive calculation. Its value is only recomputed when the dependencies change:

JSuseMemo for sorted data
const daftarUrut = useMemo(() => {
  return daftar.sort((a, b) => b.skor - a.skor);
}, [daftar]);

useCallback for Stable Functions

useCallback returns the same function reference as long as the dependencies don't change. This matters when the function is passed as a prop to a memoized child component — preventing unnecessary re-renders in episode 8.

JSuseCallback for handlers
const onKlik = useCallback(() => {
  console.log("diklik");
}, []);

Props and Composition with TypeScript

Typing Props

TypeScript makes the component contract explicit. Define props as an interface and mark optional ones with a question mark:

JSTyping props with TypeScript
type ButtonProps = {
  title: string;
  onPress: () => void;
  disabled?: boolean;
};
 
export function Button({ title, onPress, disabled = false }: ButtonProps) {
  return (
    <Pressable onPress={onPress} disabled={disabled}>
      <Text>{title}</Text>
    </Pressable>
  );
}

In the Button above, disabled? is optional with a default of false. When you call <Button title="Simpan" onPress={simpan} />, the editor immediately validates the props being passed.

Composition: Children as a Prop

JSComposition with children
type CardProps = {
  children: React.ReactNode;
};
 
export function Card({ children }: CardProps) {
  return <View style={styles.card}>{children}</View>;
}

With this pattern, <Card><Text>Konten bebas</Text></Card> works for any content. Composition is more flexible than guessing every possible prop combination.

Tip

Separate "dumb" components (which only receive props and render) from "smart" components that hold state. This structure makes logic easy to test and reuse, as you'll see in episode 20.

Closing

Episode 5 brought your app to life: state with useState, side effects with useEffect, stable values with useRef, optimization with useMemo and useCallback, and safe prop contracts thanks to TypeScript.

Key takeaways:

  • useState stores values that trigger a re-render.
  • useEffect handles side effects, with cleanup to prevent updates after unmount.
  • useRef stores stable values that don't trigger a re-render.
  • useMemo caches calculation results; useCallback stabilizes function references.
  • A controlled TextInput binds value to state.
  • Typing props with a TypeScript interface prevents many bugs before runtime.

In the next episode, episode 6, we'll discuss navigation: React Navigation with stack, tab, and drawer, the file-based Expo Router alternative, and deep linking configuration to open specific screens from a URL.

Learn React Native - State, Props & Hooks | Learn React Native