Mobile Step 2: Chat UI

Build the chat interface — message bubbles, input field, and send button.

What You'll Learn

  • React Native core components (View, Text, TextInput, TouchableOpacity)
  • Styling with StyleSheet
  • FlatList for scrollable message list
  • Layout with flexbox

Step 1: Understanding the Component Structure

Our chat app will have this layout:

┌─────────────────────┐
│   Header            │
├─────────────────────┤
│                     │
│   Message List      │
│   ┌─────────┐       │
│   │ User    │       │
│   └─────────┘       │
│   ┌──────────┐      │
│   │  AI      │      │
│   └──────────┘      │
│                     │
├─────────────────────┤
│ Typing indicator    │
├─────────────────────┤
│ [Input...]  [Send]  │
└─────────────────────┘

Step 2: Core Components

import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet } from 'react-native';

<View>              // Like <div> in web — a container
<Text>              // Display text
<TextInput />       // Text input field
<TouchableOpacity>  // Pressable button
<FlatList />        // Scrollable, optimized list
<StyleSheet>        // Defines styles

Step 3: Define the Message Interface

interface Message {
  id: number;
  content: string;
  sender: "user" | "ai";
  timestamp?: string;
}

Step 4: State for Messages and Input

import { useState } from "react";

const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
  • messages — the array of chat messages
  • input — the current text in the input field

Step 5: Render Messages with FlatList

<FlatList
  data={messages}
  keyExtractor={(item) => item.id.toString()}
  renderItem={({ item }) => {
    const isUser = item.sender === "user";
    return (
      <View style={[
        styles.bubble,
        isUser ? styles.userBubble : styles.aiBubble
      ]}>
        <Text style={isUser ? styles.userText : styles.aiText}>
          {item.content}
        </Text>
      </View>
    );
  }}
/>
  • data — the array to render
  • keyExtractor — unique key for each item
  • renderItem — function that returns the component for each item

Step 6: Input Row

<View style={styles.inputRow}>
  <TextInput
    style={styles.input}
    value={input}
    onChangeText={setInput}
    placeholder="Type a message..."
  />
  <TouchableOpacity style={styles.sendButton}>
    <Text style={styles.sendButtonText}>Send</Text>
  </TouchableOpacity>
</View>

Step 7: Styling with StyleSheet

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#F5F5F5",
  },
  bubble: {
    maxWidth: "80%",
    padding: 12,
    borderRadius: 16,
    marginBottom: 8,
  },
  userBubble: {
    backgroundColor: "#007AFF",
    alignSelf: "flex-end",
  },
  aiBubble: {
    backgroundColor: "#E8E8E8",
    alignSelf: "flex-start",
  },
  inputRow: {
    flexDirection: "row",
    padding: 12,
    borderTopWidth: 1,
    borderTopColor: "#DDD",
    backgroundColor: "#FFF",
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: "#DDD",
    borderRadius: 20,
    paddingHorizontal: 16,
    paddingVertical: 10,
    fontSize: 16,
  },
  sendButton: {
    backgroundColor: "#007AFF",
    borderRadius: 20,
    paddingHorizontal: 20,
    paddingVertical: 10,
    marginLeft: 8,
  },
  sendButtonText: {
    color: "#FFF",
    fontSize: 16,
    fontWeight: "600",
  },
});

Step 8: Build the Complete UI

Replace your App.tsx with the code from the final App.tsx (just the UI part — no API calls yet, use placeholder messages):

import React, { useState } from "react";
import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet } from "react-native";

interface Message {
  id: number;
  content: string;
  sender: "user" | "ai";
}

export default function App() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");

  const handleSend = () => {
    if (!input.trim()) return;
    const newMsg: Message = { id: Date.now(), content: input, sender: "user" };
    setMessages([...messages, newMsg]);
    setInput("");
  };

  return (
    <View style={styles.container}>
      <FlatList
        data={messages}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <View style={[styles.bubble, item.sender === "user" ? styles.userBubble : styles.aiBubble]}>
            <Text>{item.content}</Text>
          </View>
        )}
      />
      <View style={styles.inputRow}>
        <TextInput
          style={styles.input}
          value={input}
          onChangeText={setInput}
          placeholder="Type a message..."
        />
        <TouchableOpacity style={styles.sendButton} onPress={handleSend}>
          <Text style={styles.sendButtonText}>Send</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#F5F5F5", paddingTop: 50 },
  bubble: { maxWidth: "80%", padding: 12, borderRadius: 16, marginBottom: 8 },
  userBubble: { backgroundColor: "#007AFF", alignSelf: "flex-end" },
  aiBubble: { backgroundColor: "#E8E8E8", alignSelf: "flex-start" },
  inputRow: { flexDirection: "row", padding: 12, borderTopWidth: 1, borderTopColor: "#DDD" },
  input: { flex: 1, borderWidth: 1, borderColor: "#DDD", borderRadius: 20, paddingHorizontal: 16, paddingVertical: 10 },
  sendButton: { backgroundColor: "#007AFF", borderRadius: 20, paddingHorizontal: 20, paddingVertical: 10, marginLeft: 8 },
  sendButtonText: { color: "#FFF", fontSize: 16, fontWeight: "600" },
});

Verification

  • Type a message in the input field
  • Tap Send
  • The message appears as a blue bubble on the right
  • No AI reply yet (that's step 3)