BUILDING A TWO-
PANEL REACT CHAT
APPLICATION
LAYOUT
Instructor: Mr. Roldan
L. Cultura
1st Semester, S.Y.
2025–2026
Learning Objectives
After completing this module, you should be able to:
1. Explain how React components create dynamic web layouts.
2. Build a two-panel chat interface using React and CSS Flexbox.
3. Use React state and props to manage and display data.
4. Implement reusable components for conversation lists and
messages.
Lesson Overview
This module introduces how modern chat applications are built
using React.
You will learn how to:
• Break an interface into reusable components
• Use useState for dynamic data handling
• Pass data between components using props
• Design a clean horizontal layout using CSS Flexbox
The final output resembles a simple version of Messenger or
WhatsApp Web.
Key Concepts
React Components
Reusable blocks of UI (header, sidebar, message list, etc.)
State
Stores data that changes (messages, text input, active chat).
Props
Allow components to receive data from parent components.
Flexbox
CSS layout model that efficiently places elements side by side.
Code Flow Diagram
[Link]
│
├── Sidebar (conversation list)
├── ChatWindow (shows active chat messages)
└── MessageForm (input for sending)
Full Source Code with comments
[Link]
// [Link] — main component controlling layout and active chat
import React, { useState } from "react";
import ChatWindow from "./ChatWindow";
import "./[Link]";
function App() {
// Stores all available conversations
const [conversations, setConversations] = useState([
{ id: 1, name: "John Doe", messages: ["Hey!", "How are you?"] },
{ id: 2, name: "Jane Smith", messages: ["Hello!", "Let's meet later."] },
{ id: 3, name: "Group Chat", messages: ["Welcome everyone!"] },
]);
// Stores the active chat
const [activeChat, setActiveChat] = useState(conversations[0]);
// When user clicks a conversation
const handleSelectChat = (chat) => {
setActiveChat(chat);
};
return (
<div className="appContainer">
{/* LEFT PANEL — list of conversations */}
<div className="sidebar">
<h2 className="sidebarTitle">Chats</h2>
{[Link]((chat) => (
<div
key={[Link]}
className={`chatItem ${
[Link] === [Link] ? "activeChat" : ""
}`}
onClick={() => handleSelectChat(chat)}
>
<p className="chatName">{[Link]}</p>
<p className="lastMessage">
{[Link][[Link] - 1]}
</p>
</div>
))}
</div>
{/* RIGHT PANEL — active chat */}
<div className="chatArea">
<ChatWindow activeChat={activeChat} />
</div>
</div>
);
}
export default App;
[Link]
// [Link] — shows the messages of the selected chat
import React, { useState } from "react";
import MessageForm from "./MessageForm";
function ChatWindow({ activeChat }) {
// Store messages for the active chat
const [messages, setMessages] = useState([Link]);
// Add a new message
const handleSend = (newMessage) => {
setMessages([...messages, newMessage]);
};
return (
<div className="chatWindowContainer">
<div className="chatHeader">
<h2>{[Link]}</h2>
</div>
<div className="messagesContainer">
{[Link]((msg, index) => (
<p key={index} className="messageBubble">
{msg}
</p>
))}
</div>
<MessageForm onSend={handleSend} />
</div>
);
}
export default ChatWindow;
[Link]
// [Link] — input for sending messages
import React, { useState } from "react";
function MessageForm({ onSend }) {
const [text, setText] = useState("");
const handleSubmit = (e) => {
[Link](); // Prevent refresh
if ([Link]() === "") return;
onSend(text); // Send text back to ChatWindow
setText(""); // Clear input
};
return (
<form onSubmit={handleSubmit} className="messageForm">
<input
type="text"
placeholder="Type your message..."
value={text}
onChange={(e) => setText([Link])}
className="messageInput"
/>
<button type="submit" className="sendButton">Send</button>
</form>
);
}
export default MessageForm;
CSS Styling padding: 1rem;
/* [Link] — Layout & Design */ cursor: pointer;
border-bottom: 1px solid #eee;
.appContainer { }
display: flex;
height: 100vh; .chatItem:hover {
background-color: #f5f7fb; background: #f0f2f5;
} }
/* LEFT PANEL */ .activeChat {
.sidebar { background: #dbeafe;
width: 30%; }
background: #fff;
border-right: 1px solid #ddd; /* RIGHT PANEL */
overflow-y: auto; .chatArea {
display: flex; width: 70%;
flex-direction: column; display: flex;
} flex-direction: column;
}
.sidebarTitle {
padding: 1rem; .messagesContainer {
border-bottom: 1px solid #ddd; flex: 1;
font-size: 1.5rem; padding: 1rem;
font-weight: bold; overflow-y: auto;
} background: #eef2ff;
}
.chatItem {
React Components, Props,
and State Management
(Foundations)
LEARNING OBJECTIVES
At the end of Module 2, students should be able to:
1. Explain the concept of React Components and their role in
building user interfaces.
2. Differentiate between Functional Components and Class
Components (with focus on modern functional components).
3. Demonstrate how Props pass data from parent to child
components.
4. Apply useState to store dynamic values inside components.
5. Establish clear parent–child relationships through code.
6. Build small UI features using proper component structure.
LESSON OVERVIEW
React is built on the idea that user interfaces are best created from small, reusable
components.
Instead of writing a single large HTML file, React encourages developers to:
• Break down interfaces into smaller components
• Give each component a specific job
• Let components communicate through props
• Let components remember data using state
This module strengthens your understanding of how React apps are structured — a
foundation for future modules such as Firebase integration and dynamic chat features.
Key Concepts in Module 2
1. What Is a Component?
A component is a reusable UI block.
Examples:
• Sidebar
• Header
• Message bubbles
• Message input field
Components improve:
• Organization
• Readability
• Reusability
2. Types of Components
Modern React mainly uses Functional Components:
function Header() {
return <h1>Welcome</h1>;
}
Older React used Class Components, but they are no longer the standard.
We will focus only on Functional Components.
3. Props (Properties)
Props are used to send data from a parent component to a child component.
Example:
function Greeting(props) {
return <h2>Hello, {[Link]}</h2>;
}
<Greeting name="Roldan" />
Output:
Hello, Roldan
Props are read-only.
The child cannot change the values.
4. State
State is data that a component controls and can change.
Example:
const [count, setCount] = useState(0);
• count = current value
• setCount() = function to update the value
State is used when something changes in the UI:
• Messages added
• Text input typed
• Buttons clicked
5. Parent–Child Relationship
[Link] (parent)
│
└── [Link] (child)
The parent can send data through props.
The child can return data using a callback function.
This is how onSend() worked in your Module 1 chat app.
Below is a simple example for practicing components, props, and state.
[Link]
import React, { useState } from "react";
import DisplayMessage from "./DisplayMessage";
import MessageInput from "./MessageInput";
function App() {
// Store the message typed by the student
const [message, setMessage] = useState("");
// Receive message from child and update state
const handleReceive = (newMessage) => {
setMessage(newMessage);
};
return (
<div>
<h1>Module 2 Demo</h1>
{/* Child component sends message upward */}
<MessageInput onSend={handleReceive} />
{/* Display component receives message through props */}
<DisplayMessage text={message} />
</div>
);
}
export default App;
[Link]
import React, { useState } from "react";
function MessageInput({ onSend }) {
const [input, setInput] = useState("");
const handleSubmit = (e) => {
[Link]();
if ([Link]() === "") return;
onSend(input); // send data to parent
setInput(""); // clear field
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Type something..."
value={input}
onChange={(e) => setInput([Link])}
/>
<button>Send</button>
</form>
);
}
export default MessageInput;
[Link]
import React from "react";
function DisplayMessage({ text }) {
return (
<div>
<h3>You typed:</h3>
<p>{text}</p>
</div>
);
}
export default DisplayMessage;
This simple app demonstrates the core React workflow:
1. Child → sends data → Parent
2. Parent → stores data → State
3. Parent → sends data → Display child
This structure is the foundation of more advanced apps.
React Events, Lists,
and Conditional
Rendering
LEARNING OBJECTIVES
After completing this module, students should be able to:
1. Use React event handlers (onClick, onChange, onSubmit).
2. Display lists of data using JavaScript’s .map() function.
3. Use keys to uniquely identify list items.
4. Apply conditional rendering to show or hide UI elements.
5. Build dynamic behavior that responds to user actions.
6. Prepare for more complex logic in Module 4 (data structures,
timestamps, Firebase).
LESSON OVERVIEW
This module focuses on three essential behaviors in React:
1. Events
React uses event handlers to respond to user actions:
• Clicking
• Typing
• Submitting forms
• Selecting items
Events look like HTML events but are written in camelCase:
onClick
onChange
onSubmit
2. Lists
Most real applications show lists of data:
• Messages
• Conversations
• Notifications
• Contacts
React uses JavaScript’s .map() to loop through arrays and display elements.
3. Conditional Rendering
React can show UI only when certain conditions are met:
• If there are no messages
• If the user is not logged in
• If an error occurs
• If a chat is active
Conditions use expressions like:
text === ""
isLoggedIn
[Link] > 0
activeChat ? ...
These three concepts allow your chat app to behave more like a real application.
1. React Events
React uses functions to respond to user interactions.
Example:
<button onClick={handleClick}>Click</button>
Event example functions:
function handleClick() { ... }
function handleSubmit(e) { ... }
function handleChange(e) { ... }
2. Mapping Lists in React
.map() transforms arrays into UI elements.
Example:
const names = ["Roldan", "Marie", "Jude"];
[Link]((n) => <p>{n}</p>);
Rendering messages:
[Link]((m, index) => (
<p key={index}>{m}</p>
));
3. Key Prop
A key prevents React errors and helps track items during updates.
Good key examples:
• Database ID
• Unique number
• Index (last resort)
4. Conditional Rendering
Show UI only when needed.
Example 1 – Using if statements:
if ([Link] === 0) {
return <p>No messages yet</p>;
}
Example 2 – Using ternary:
activeChat ? <ChatWindow /> : <p>Select a chat</p>;
Example 3 – Using &&:
isTyping && <p>User is typing...</p>;
[Link]
import React, { useState } from "react";
function App() {
const [messages, setMessages] = useState([]);
const [text, setText] = useState("");
const handleSubmit = (e) => {
[Link]();
if ([Link]() === "") return;
// Add message to array
setMessages([...messages, text]);
setText("");
};
return (
<div>
<h1>Module 3 Example</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Type message..."
value={text}
onChange={(e) => setText([Link])}
/>
<button>Send</button>
</form>
{/* Conditional Rendering */}
{[Link] === 0 ? (
<p>No messages yet</p>
):(
<div>
<h3>Messages:</h3>
{[Link]((msg, i) => (
<p key={i}>{msg}</p>
))}
</div>
)}
</div>
);
}
export default App;
EXPLANATION
1. Event Handling
Typing in the input uses:
onChange={(e) => setText([Link])}
This updates React state each time the user types.
2. Submitting the Form
onSubmit={handleSubmit}
The handleSubmit function:
• Prevents page reload
• Adds text to messages array
• Clears input
3. Mapping Messages
[Link]((msg, i) => <p key={i}>{msg}</p>)
Each message becomes a <p> element.
4. Conditional Rendering
[Link] === 0 ? <p>No messages yet</p> : ...
Advanced State,
Data Flow, and
Managing Messages
in React
LEARNING OBJECTIVES
After completing Module 4, students should be able to:
1. Explain how state flows between components in a multi-component React app.
2. Use lifting state up to let parent components manage child data.
3. Understand one-way data flow in React.
4. Manage arrays of messages using spread syntax and state updates.
5. Add timestamps to messages using JavaScript Date functions.
6. Prepare the code structure required before integrating Firebase in Module 5.
LESSON OVERVIEW
This module focuses on the advanced behaviors of state and data flow in React.
You will understand how:
• [Link] controls global data
• Child components send data back to [Link]
• Messages are stored, updated, and displayed
• React updates the UI in response to state changes
• Timestamps are added to messages
These concepts make the chat application closer to a real messaging system.
Key Concepts
1. One-Way Data Flow
React sends data downward (parent → child).
Example:
<ChatWindow messages={messages} />
2. Lifting State Up
When two child components need to share data, the data must live in the parent.
Why?
• Only parents can coordinate multiple children
• Ensures consistent and synchronized UI
Example:
[Link]
│
├── [Link]
└── [Link]
3. Managing Arrays in State
React state must be updated immutably.
Correct:
setMessages([...messages, newMessage]);
Incorrect:
[Link](newMessage); // does not re-render
4. Adding Timestamps
You generate timestamps using:
const time = new Date().toLocaleTimeString();
In a real app, timestamps allow:
• message sorting
• correct conversation display
• real-time updates (used in Module 5 with Firebase)
UPDATED SAMPLE CODE FOR MODULE 4
(No Firebase yet — Firebase comes in Module 5.)
[Link]
Handles conversations, active chat, and message updates.
import React, { useState } from "react";
import ChatWindow from "./ChatWindow";
import "./[Link]";
function App() {
const [conversations, setConversations] = useState([
{ id: 1, name: "John Doe", messages: [] },
{ id: 2, name: "Jane Smith", messages: [] }
]);
const [activeChatId, setActiveChatId] = useState(1);
const handleSendMessage = (chatId, messageText) => {
const time = new Date().toLocaleTimeString();
setConversations((prevChats) =>
[Link]((chat) =>
[Link] === chatId
?{
...chat,
messages: [...[Link], { text: messageText, time }]
}
: chat
)
);
};
const activeChat = [Link]((c) => [Link] === activeChatId);
return (
<div className="appContainer">
<div className="sidebar">
{[Link]((chat) => (
<div
key={[Link]}
className={`chatItem ${
[Link] === activeChatId ? "activeChat" : ""
}`}
onClick={() => setActiveChatId([Link])}
>
<p>{[Link]}</p>
</div>
))}
</div>
<ChatWindow
chat={activeChat}
onSend={(msg) => handleSendMessage(activeChatId, msg)}
/>
</div>
);
}
export default App;
[Link]
Displays messages + timestamp.
import React, { useState } from "react";
import MessageForm from "./MessageForm";
function ChatWindow({ chat, onSend }) {
const messages = [Link];
return (
<div className="chatWindowContainer">
<div className="chatHeader">
<h2>{[Link]}</h2>
</div>
<div className="messagesContainer">
{[Link] === 0 ? (
<p className="empty">No messages yet</p>
):(
[Link]((m, i) => (
<div key={i} className="messageBubble">
<p>{[Link]}</p>
<span className="timestamp">{[Link]}</span>
</div>
))
)}
</div>
<MessageForm onSend={onSend} />
</div>
);
}
export default ChatWindow;
[Link]
import React, { useState } from "react";
function MessageForm({ onSend }) {
const [text, setText] = useState("");
const handleSubmit = (e) => {
[Link]();
if ([Link]() === "") return;
onSend(text);
setText("");
};
return (
<form onSubmit={handleSubmit} className="messageForm">
<input
type="text"
placeholder="Type your message..."
value={text}
onChange={(e) => setText([Link])}
className="messageInput"
/>
<button className="sendButton">Send</button>
</form>
);
}
export default MessageForm;
CODE EXPLANATION
1. Why state lives in [Link]
Because [Link] controls:
• Active chat selection
• Conversation list
• Message history for each chat
If state were inside MessageForm or ChatWindow, you could not:
• Keep messages after switching chats
• Share data between components
• Update the sidebar preview
[Link] = global controller
ChatWindow = display
MessageForm = input
2. Why we use spread: [...messages]
React must receive a new array to trigger updates.
push() modifies the existing array → React does NOT detect change.
[...] creates a new array → React re-renders.
3. Why timestamp is added here
Timestamps will later be stored in Firebase.
Using:
new Date().toLocaleTimeString()
gives a readable time like:
3:42 PM
Connecting React to
Firebase
Authentication &
Firestore
LEARNING OBJECTIVES
After completing Module 5, students should be able to:
1. Set up a Firebase project in the Firebase Console.
2. Connect a React app to Firebase using the official SDK.
3. Use Firebase Authentication to log in with Google.
4. Store chat messages in Cloud Firestore.
5. Read messages from Firestore in real time using onSnapshot.
6. Understand how Firebase works with React’s data flow.
7. Replace local array-based messages with database-driven messages.
LESSON OVERVIEW
In Modules 1–4, your chat application used:
• Local React State
• Temporary Data
• Fake conversations
• No user accounts
• No real database
Now, in Module 5, your app becomes real and online, because you will use:
✔ Firebase Authentication
To sign in users using Google Login.
✔ Cloud Firestore
To store real chat messages in the cloud.
✔ Real Time Listeners
To update UI instantly when new messages are saved.
Why Firebase?
• No need to build a backend server
• Automatic scaling
• Real-time updates without refreshing
• Easy authentication
• Secure and structured NoSQL database
Firebase is the perfect backend for student projects and professional prototypes.
FIREBASE SETUP GUIDE (Step-by-Step)
Follow these steps EXACTLY:
1. Create a Firebase Project
1. Go to: [Link]
2. Click Add Project
3. Type project name (e.g., ChatApp-2025)
4. Disable Google Analytics (optional)
5. Click Create Project
2. Add a Web App
1. In Firebase Console → Project Overview
2. Click </> (Web)
3. App nickname: react-chat-app
4. Register app
5. You will receive this configuration:
const firebaseConfig = {
apiKey: "...",
authDomain: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "..."
};
Copy it — you will use it soon.
3. Enable Authentication
1. Go to Build → Authentication
2. Click Get Started
3. Go to Sign-in method
4. Enable Google Sign-In
5. Save
4. Create a Firestore Database
1. Go to Firestore Database
2. Click Create Database
3. Choose Start in Production Mode
4. Select nearest region
5. Create
ADD FIREBASE TO YOUR REACT PROJECT
Inside your project folder:
Install Firebase SDK
npm install firebase
Create a new file: [Link]
// [Link]
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
const firebaseConfig = {
apiKey: "YOUR KEY",
authDomain: "YOUR DOMAIN",
projectId: "YOUR PROJECT ID",
storageBucket: "YOUR BUCKET",
messagingSenderId: "YOUR SENDER ID",
appId: "YOUR APP ID"
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
export const provider = new GoogleAuthProvider();
export const signInWithGoogle = () => signInWithPopup(auth, provider);
This file handles:
• Firebase initialization
• Firestore database
• Google authentication
ADD GOOGLE LOGIN TO [Link]
Modify your existing [Link]:
// [Link]
import React, { useState, useEffect } from "react";
import { auth, signInWithGoogle } from "./firebase";
import { onAuthStateChanged } from "firebase/auth";
import ChatWindow from "./ChatWindow";
import LoginScreen from "./LoginScreen";
import "./[Link]";
function App() {
const [user, setUser] = useState(null);
// Track user login status
useEffect(() => {
onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
}, []);
if (!user) {
return <LoginScreen onLogin={signInWithGoogle} />;
}
return <ChatWindow user={user} />;
}
export default App;
[Link]
function LoginScreen({ onLogin }) {
return (
<div className="loginContainer">
<h2>Welcome to Chat App</h2>
<button className="loginButton" onClick={onLogin}>
Sign in with Google
</button>
</div>
);
}
export default LoginScreen;
SAVING AND LOADING MESSAGES IN FIRESTORE
[Link] (Firestore version)
import React, { useEffect, useState } from "react";
import { db } from "./firebase";
import {
collection,
addDoc,
orderBy,
query,
onSnapshot,
serverTimestamp
} from "firebase/firestore";
import MessageForm from "./MessageForm";
function ChatWindow({ user }) {
const [messages, setMessages] = useState([]);
// Load messages in real time
useEffect(() => {
const q = query(
collection(db, "messages"),
orderBy("timestamp", "asc")
);
onSnapshot(q, (snapshot) => {
setMessages([Link]((doc) => [Link]()));
});
}, []);
// Send message to Firestore
const sendMessage = async (text) => {
await addDoc(collection(db, "messages"), {
text: text,
sender: [Link],
timestamp: serverTimestamp()
});
};
return (
<div className="chatWindowContainer">
<h2>Chat Room</h2>
<div className="messagesContainer">
{[Link]((m, i) => (
<div key={i} className="messageBubble">
<p><strong>{[Link]}:</strong> {[Link]}</p>
</div>
))}
</div>
<MessageForm onSend={sendMessage} />
</div>
);
}
export default ChatWindow;
[Link]
(Same structure, but now sends to Firestore)
import React, { useState } from "react";
function MessageForm({ onSend }) {
const [text, setText] = useState("");
const handleSubmit = (e) => {
[Link]();
if ([Link]() === "") return;
onSend(text);
setText("");
};
return (
<form onSubmit={handleSubmit} className="messageForm">
<input
type="text"
placeholder="Type message..."
value={text}
onChange={(e) => setText([Link])}
className="messageInput"
/>
<button className="sendButton">Send</button>
</form>
);
}
export default MessageForm;
HOW REAL-TIME FIRESTORE WORKS
1. Writing data (addDoc)
When the user sends a message:
addDoc(collection(db, "messages"), {
text,
sender,
timestamp: serverTimestamp()
});
2. Reading data (onSnapshot)
React listens for new messages:
onSnapshot(q, (snapshot) => {
setMessages([Link](doc => [Link]()));
});
3. Firebase automatically updates UI
• No refresh required
• Messages appear instantly
• Multiple users can chat at the same time
This is the same technology used by:
• Messenger
• WhatsApp Web
• Instagram Chat