Cours React Native · Module 4: État et données d'API

useState et useReducer - Le coffre-fort de l'état

3 min de lecture ·
Dans cette leçon3

Bienvenue dans le coffre-fort de l'Atlantide ! C'est ici que nous stockons les données les plus précieuses de notre application. Commençons par les hooks de base pour gérer l'état.

useState - Le coffre-fort basique

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

function TreasureCounter() {
  // État numérique simple
  const [treasures, setTreasures] = useState(0);

  // État de type objet
  const [treasure, setTreasure] = useState({
    gold: 0,
    pearls: 0,
    gems: 0,
  });

  // État de type tableau
  const [collectedItems, setCollectedItems] = useState<string[]>([]);

  // Mettre à jour une valeur unique
  const addTreasure = () => {
    setTreasures(prev => prev + 1);
  };

  // Mettre à jour un objet (immutabilité !)
  const addGold = () => {
    setTreasure(prev => ({
      ...prev,
      gold: prev.gold + 10,
    }));
  };

  // Ajouter à un tableau
  const collectItem = (item: string) => {
    setCollectedItems(prev => [...prev, item]);
  };

  // Retirer d'un tableau
  const removeItem = (index: number) => {
    setCollectedItems(prev => prev.filter((_, i) => i !== index));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Coffre-fort : {treasures} trésors</Text>
      <Pressable style={styles.button} onPress={addTreasure}>
        <Text style={styles.buttonText}>Ajouter un trésor</Text>
      </Pressable>

      <Text>Or : {treasure.gold}</Text>
      <Pressable onPress={addGold}>
        <Text>Récolter de l'or</Text>
      </Pressable>

      <Text>Objets : {collectedItems.join(', ')}</Text>
      <Pressable onPress={() => collectItem('Perle')}>
        <Text>Récolter une perle</Text>
      </Pressable>
    </View>
  );
}

Initialisation paresseuse de l'état

// Pour les opérations d'initialisation coûteuses
const [data, setData] = useState(() => {
  // Cette fonction ne s'exécute qu'une seule fois, au premier rendu
  return expensiveComputation();
});

// Exemple : chargement depuis 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 - Pour un état complexe

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;
  }
}

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

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

  return (
    <View>
      <Text>Or : {state.gold}</Text>
      <Text>Perles : {state.pearls}</Text>

      <Pressable onPress={() => dispatch({ type: 'ADD_GOLD', amount: 100 })}>
        <Text>Ajouter 100 d'or</Text>
      </Pressable>

      <Pressable onPress={() => dispatch({ type: 'ADD_PEARL' })}>
        <Text>Ajouter une perle</Text>
      </Pressable>

      <Pressable onPress={() => dispatch({ type: 'RESET' })}>
        <Text>Réinitialiser le coffre</Text>
      </Pressable>
    </View>
  );
}

useState est parfait pour un état simple, useReducer pour une logique complexe comportant de nombreuses actions.

Code de cette leçon : App.tsx
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';

export default function App() {
  // TODO : crée l'état du compteur de trésors avec une valeur initiale de 0
  // const [treasures, setTreasures] = useState(0);

  const addTreasure = () => {
    // TODO : augmente le compteur de 1 à l'aide de la fonction de mise à jour
    // setTreasures(prev => prev + 1);
  };

  const removeTreasure = () => {
    // TODO : diminue le compteur de 1, mais ne descends jamais en dessous de 0
    // setTreasures(prev => (prev > 0 ? prev - 1 : 0));
  };

  return (
    <View style={styles.container}>
      {/* TODO : affiche le nombre actuel de trésors à la place de 0 */}
      <Text style={styles.title}>Trésorerie : 0 trésor</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',
  },
});

Entrées de la documentation utilisées dans cette leçon

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Comment initialise-t-on correctement useState avec une valeur initiale ?

  2. 2. Comment met-on correctement à jour un seul champ d'un état de type objet ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Tri horizontal

    Reconstitue la mise à jour d'état correcte en préservant l'immutabilité :

  • Clique dans l'ordre

    Clique sur les éléments qui créent une action pour useReducer :

  • Éditeur de code

    Complète le code du compteur avec les fonctions d'ajout et de retrait.

Articles utiles