React Native course ยท Module 4: State and API data

useState and useReducer - The State Vault

3 min read ยท
In this lesson3

Welcome to the vault of Atlantis! This is where we store the most precious data of our application. Let's start with the basic state management hooks.

useState - The Basic Vault

import { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';

function TreasureCounter() {
  // Simple numeric state
  const [treasures, setTreasures] = useState(0);

  // Object state
  const [treasure, setTreasure] = useState({
    gold: 0,
    pearls: 0,
    gems: 0,
  });

  // Array state
  const [collectedItems, setCollectedItems] = useState<string[]>([]);

  // Updating a single value
  const addTreasure = () => {
    setTreasures(prev => prev + 1);
  };

  // Updating an object (immutability!)
  const addGold = () => {
    setTreasure(prev => ({
      ...prev,
      gold: prev.gold + 10,
    }));
  };

  // Adding to an array
  const collectItem = (item: string) => {
    setCollectedItems(prev => [...prev, item]);
  };

  // Removing from an array
  const removeItem = (index: number) => {
    setCollectedItems(prev => prev.filter((_, i) => i !== index));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Vault: {treasures} treasures</Text>
      <Pressable style={styles.button} onPress={addTreasure}>
        <Text style={styles.buttonText}>Add treasure</Text>
      </Pressable>

      <Text>Gold: {treasure.gold}</Text>
      <Pressable onPress={addGold}>
        <Text>Collect gold</Text>
      </Pressable>

      <Text>Items: {collectedItems.join(', ')}</Text>
      <Pressable onPress={() => collectItem('Pearl')}>
        <Text>Collect pearl</Text>
      </Pressable>
    </View>
  );
}

Lazy State Initialization

// For expensive initialization operations
const [data, setData] = useState(() => {
  // This function runs only once on the first render
  return expensiveComputation();
});

// Example: loading from AsyncStorage
const [settings, setSettings] = useState<Settings | null>(null);

useEffect(() => {
  const loadSettings = async () => {
    const saved = await AsyncStorage.getItem('settings');
    if (saved) {
      setSettings(JSON.parse(saved));
    }
  };
  loadSettings();
}, []);

useReducer - For Complex State

import { useReducer } from 'react';

// Types
interface TreasureState {
  gold: number;
  pearls: number;
  gems: number;
  items: string[];
  isLoading: boolean;
  error: string | null;
}

type TreasureAction =
  | { type: 'ADD_GOLD'; amount: number }
  | { type: 'ADD_PEARL' }
  | { type: 'ADD_GEM'; count: number }
  | { type: 'COLLECT_ITEM'; item: string }
  | { type: 'SET_LOADING'; isLoading: boolean }
  | { type: 'SET_ERROR'; error: string }
  | { type: 'RESET' };

// Reducer
function treasureReducer(state: TreasureState, action: TreasureAction): TreasureState {
  switch (action.type) {
    case 'ADD_GOLD':
      return { ...state, gold: state.gold + action.amount };
    case 'ADD_PEARL':
      return { ...state, pearls: state.pearls + 1 };
    case 'ADD_GEM':
      return { ...state, gems: state.gems + action.count };
    case 'COLLECT_ITEM':
      return { ...state, items: [...state.items, action.item] };
    case 'SET_LOADING':
      return { ...state, isLoading: action.isLoading };
    case 'SET_ERROR':
      return { ...state, error: action.error, isLoading: false };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

// Initial state
const initialState: TreasureState = {
  gold: 0,
  pearls: 0,
  gems: 0,
  items: [],
  isLoading: false,
  error: null,
};

// Component
function TreasureVault() {
  const [state, dispatch] = useReducer(treasureReducer, initialState);

  return (
    <View>
      <Text>Gold: {state.gold}</Text>
      <Text>Pearls: {state.pearls}</Text>

      <Pressable onPress={() => dispatch({ type: 'ADD_GOLD', amount: 100 })}>
        <Text>Add 100 gold</Text>
      </Pressable>

      <Pressable onPress={() => dispatch({ type: 'ADD_PEARL' })}>
        <Text>Add pearl</Text>
      </Pressable>

      <Pressable onPress={() => dispatch({ type: 'RESET' })}>
        <Text>Reset vault</Text>
      </Pressable>
    </View>
  );
}

useState works great for simple state, useReducer for complex logic with many actions.

Code for this lesson: App.tsx
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';

export default function App() {
  // TODO: create the treasure counter state with an initial value of 0
  // const [treasures, setTreasures] = useState(0);

  const addTreasure = () => {
    // TODO: increase the counter by 1 using the updater function
    // setTreasures(prev => prev + 1);
  };

  const removeTreasure = () => {
    // TODO: decrease the counter by 1, but never go below 0
    // setTreasures(prev => (prev > 0 ? prev - 1 : 0));
  };

  return (
    <View style={styles.container}>
      {/* TODO: display the current number of treasures instead of 0 */}
      <Text style={styles.title}>Treasury: 0 treasures</Text>

      <View style={styles.row}>
        <Pressable style={styles.button} onPress={removeTreasure}>
          <Text style={styles.buttonText}>-</Text>
        </Pressable>
        <Pressable style={styles.button} onPress={addTreasure}>
          <Text style={styles.buttonText}>+</Text>
        </Pressable>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#1a5276',
  },
  title: {
    fontSize: 22,
    color: '#fff',
    marginBottom: 24,
  },
  row: {
    flexDirection: 'row',
    gap: 16,
  },
  button: {
    paddingVertical: 14,
    paddingHorizontal: 28,
    borderRadius: 8,
    backgroundColor: '#2980b9',
  },
  buttonText: {
    color: '#fff',
    fontSize: 20,
    fontWeight: '600',
  },
});

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How do you correctly initialize useState with an initial value?

  2. 2. How do you correctly update a single field in a state object?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Horizontal ordering

    Arrange the correct state update preserving immutability:

  • Click in order

    Click the elements that create an action for useReducer:

  • Code editor

    Complete the counter code with add and subtract functions.

Useful articles