Mobile Step 4: State Management

Manage loading states, error states, and message updates properly.

What You'll Learn

  • Multiple state variables for different UI states
  • Optimistic UI updates
  • Loading indicators
  • Error display

Step 1: Add State Variables

const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

Three types of state: - Data: messages — the chat content - UI: loading — whether a request is in progress - Error: error — user-visible error message

Step 2: The Send Flow

1. User types message and taps Send
2. Immediately clear input and show loading
3. Optimistically add message to the list
4. Send request to backend
5a. Success → update message with server response + add AI reply
5b. Failure → mark message as failed, show error
6. Hide loading

Step 3: Disable Send While Loading

<TouchableOpacity
  style={[styles.sendButton, loading && styles.sendButtonDisabled]}
  onPress={handleSend}
  disabled={loading}
>
  <Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>

Step 4: Show an Error Banner

{error && (
  <View style={styles.errorBanner}>
    <Text style={styles.errorText}>Error: {error}</Text>
    <TouchableOpacity onPress={() => setError(null)}>
      <Text style={styles.errorDismiss}>X</Text>
    </TouchableOpacity>
  </View>
)}

Styles:

errorBanner: {
  backgroundColor: "#FFE0E0",
  flexDirection: "row",
  justifyContent: "space-between",
  alignItems: "center",
  padding: 10,
},
errorText: { color: "#D00", flex: 1 },
errorDismiss: { color: "#D00", fontWeight: "bold", marginLeft: 10, fontSize: 18 },

Step 5: Add a Header

<View style={styles.header}>
  <Text style={styles.headerTitle}>Agentic Chat</Text>
</View>

Styles:

header: {
  backgroundColor: "#007AFF",
  paddingTop: 50,
  paddingBottom: 15,
  paddingHorizontal: 16,
},
headerTitle: { color: "#FFF", fontSize: 20, fontWeight: "bold" },

Step 6: Handle Keyboard

import { KeyboardAvoidingView, Platform } from "react-native";

<KeyboardAvoidingView
  style={styles.container}
  behavior={Platform.OS === "ios" ? "padding" : "height"}
>
  {/* everything */}
</KeyboardAvoidingView>

This pushes the input row up when the keyboard opens instead of covering it.

Step 7: Understanding useState Updates

// ❌ Wrong — doesn't work reliably with FlatList
messages.push(newMessage);
setMessages(messages);

// ✅ Correct — creates a new array
setMessages([...messages, newMessage]);

// ✅ Correct — uses the previous state
setMessages((prev) => [...prev, newMessage]);

Always create a new array/object instead of mutating the existing one. React detects changes by reference — it needs a new reference to know to re-render.

Step 8: Test the Complete Flow

  1. Send a message → input clears, loading shows
  2. Wait for response → message + AI reply appear
  3. Cause a network error (stop the backend) → error banner shows, message marked as failed
  4. Dismiss error → banner disappears