Rabikant
Posted on March 9th
Build a Real-Time Chat App in React Native with PieSocket
"Let's Learn How To Build a Real-Time Chat App in React Native with PieSocket"
If you've ever wanted to build a real-time chat app in React Native, this guide is for you! We'll walk through how to build a clean and modern chat interface that supports real-time messaging using PieSocket — a scalable and developer-friendly WebSocket solution
By the end of this tutorial, you’ll have a fully functional chat app that works in real time with messages aligned neatly for both sender and receiver.
Prerequisites
Make sure you have the following set up:
- Node.js and npm/yarn installed
- React Native CLI or Expo
- A PieSocket account (create one for free at PieSocket)
- React Native development environment (iOS/Android)
Step 1: Initialize Your React Native App
npx react-native init ChatApp
cd ChatApp
Install dependencies:
npm install piesocket-js react-native-get-random-values
Note: react-native-get-random-values is used to generate unique user IDs.
State and References Initialization
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const [connected, setConnected] = useState(false);
const piesocketRef = useRef(null);
const usernameRef = useRef('User_' + Math.floor(Math.random() * 1000));
What this does:
- message: The current message the user is typing.
- messages: An array of all chat messages.
- connected: Boolean to track if connection to PieSocket was successful.
- piesocketRef: A ref to store the active PieSocket channel instance.
- usernameRef: Generates and stores a random username like User_123.
useEffect Hook to Setup PieSocket
useEffect(() => {
const piesocket = new PieSocket({
apiKey: '',
clusterId: '',
notifySelf: true,
presence: true,
userId: usernameRef.current,
});
What this does:
- Initializes the PieSocket connection with the given credentials (apiKey, clusterId, etc.).
- notifySelf: true: Allows this client to receive its own published messages.
- presence: true: Enables presence features (tracking who's online).
- userId: Tells PieSocket which user is currently connected.
Note: Replace '' and '' with actual apiKey and clusterId.
Subscribing to the Chat Room Channel
piesocket.subscribe('chat-room').then((channel) => {
piesocketRef.current = channel;
setConnected(true);
What this does:
- Subscribes the client to a channel named "chat-room".
- Saves the channel reference in piesocketRef for future use.
- Sets connected = true so the app knows PieSocket is ready.
Listening to New Messages
channel.listen('new_message', (data) => {
setMessages((prev) => [
{
id: Date.now().toString() + Math.random(),
text: data.text,
user: data.sender,
createdAt: new Date(),
},
...prev,
]);
});
What this does:
- Listens for an event named new_message.
- When a new message arrives, it's added to the top of the messages array.
- Each message has:
- id: Unique ID.
- text: Message content.
- user: Sender's username.
- createdAt: Timestamp for display.
Listening for New Members Joining
channel.listen('system:member_joined', (data) => {
console.log(`${data.member.user} joined the chat`);
});
What this does:
- Listens for presence events (someone joining).
- Logs a message like: User_456 joined the chat.
Cleanup on Unmount
return () => {
if (piesocketRef.current) {
piesocketRef.current.disconnect();
}
};
}, []);
What this does:
- On component unmount, it disconnects from the PieSocket channel to avoid memory leaks or open socket connections.
Sending a Message
const sendMessage = () => {
if (!message.trim() || !piesocketRef.current) return;
piesocketRef.current.publish('new_message', {
sender: usernameRef.current,
text: message,
});
setMessage('');
};
What this does:
Checks if message is empty:
!message.trim() ensures blank messages aren't sent.
Checks if PieSocket is connected:
piesocketRef.current must exist to send a message.
Publishes the message to the channel:
Using piesocketRef.current.publish(), it sends:
- sender: the current user (User_123)
- text: the message string
Clears the input field after sending with setMessage('').
Rendering Each Chat Message
const renderItem = ({ item }) => {
const isMyMessage = item.user === usernameRef.current;
return (
<Viewstyle={[
styles.messageRow,
isMyMessage ? styles.messageRowRight : styles.messageRowLeft,
]}
>
<Viewstyle={[
styles.messageBubble,
isMyMessage ? styles.myMessage : styles.otherMessage,
]}
>
<Text style={styles.username}>
{isMyMessage ? 'You' : item.user}
</Text>
<Text style={styles.messageText}>{item.text}</Text>
<Text style={styles.timestamp}>
{item.createdAt.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
</Text>
</View>
</View>
);
};
What this does:
- isMyMessage: Checks if the message is from the current user.
- Layout Alignment:
- messageRowRight: Aligns messages you sent to the right.
- messageRowLeft: Aligns messages from others to the left.
- Bubble Styling:
- myMessage: Greenish background bubble (sent by you).
- otherMessage: Grey background (sent by others).
- Username Display:
- "You" if it’s your own message.
- Otherwise shows the sender’s username.
- Message Content:
- item.text: The actual message.
- item.createdAt: Time the message was created, shown in HH:MM format.
Handle Keyboard Gracefully
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}
>
What's happening:
- This view helps prevent the keyboard from covering inputs.
- It behaves differently on iOS vs Android:
- iOS: adds padding when keyboard appears.
- Android: adjusts height.
- It wraps the entire chat layout so the input box stays visible when typing.
Displaying Chat Messages
<FlatList
data={messages}
renderItem={renderItem}
keyExtractor={(item) => item.id}
inverted
contentContainerStyle={{ padding: 10 }}
/>
What each prop does:
- data={messages}: The list of messages to show.
- renderItem={renderItem}: A function to render each message (we covered this earlier).
- keyExtractor={(item) => item.id}: Ensures each message has a unique key (for performance).
- inverted: Flips the list vertically so new messages appear at the bottom.
- contentContainerStyle: Adds padding inside the chat area.
Input Section — Typing and Sending Messages
<View style={styles.inputContainer}>
<TextInputstyle={styles.input}
placeholder="Type a message..."
placeholderTextColor="#888"
value={message}
onChangeText={setMessage}
/>
<TouchableOpacitystyle={[
styles.sendButton,
{ backgroundColor: connected ? '#4CAF50' : '#aaa' },
]}
onPress={sendMessage}
disabled={!connected}
>
<Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>
</View>
What's going on here:
- TextInput: The message box where users type text.
- Bound to message via value.
- onChangeText updates state.
- TouchableOpacity: The Send button.
- Green (#4CAF50) if connected, grey if not.
- disabled={!connected} disables the button if not connected to PieSocket.
- onPress={sendMessage} triggers the send function we explained earlier.
Main Container Layout
container: {
flex: 1,
backgroundColor: '#f4f4f4',
},
- flex: 1 → Fills the entire screen vertically.
- backgroundColor: '#f4f4f4' → Light grey background for the chat screen.
Message Row Placement
messageRow: {
flexDirection: 'row',
marginVertical: 5,
},
messageRowLeft: {
justifyContent: 'flex-start',
},
messageRowRight: {
justifyContent: 'flex-end',
},
- messageRow sets messages in a horizontal row.
- messageRowLeft and messageRowRight determine if the message aligns left (other users) or right (your messages).
Message Bubble Styles
messageBubble: {
padding: 10,
borderRadius: 10,
maxWidth: '75%',
},
myMessage: {
backgroundColor: '#dcf8c6',
alignSelf: 'flex-end',
},
otherMessage: {
backgroundColor: '#e5e5ea',
alignSelf: 'flex-start',
},
- messageBubble → Common styling for all bubbles (padding, rounded corners, max width).
- myMessage → Light green (#dcf8c6), aligned to the right.
- otherMessage → Light grey (#e5e5ea), aligned to the left.
Text Styles Inside the Bubbles
username: {
fontWeight: 'bold',
fontSize: 12,
color: '#555',
marginBottom: 2,
},
messageText: {
fontSize: 16,
color: '#333',
},
timestamp: {
fontSize: 10,
color: '#777',
marginTop: 4,
textAlign: 'right',
},
- username → Small bold name above the message.
- messageText → The main chat message text.
- timestamp → Shows the time under the message, aligned to the right.
Input Section Styles (Text box + Send Button)
inputContainer: {
flexDirection: 'row',
padding: 10,
borderTopWidth: 1,
borderColor: '#ddd',
backgroundColor: '#fff',
alignItems: 'center',
},
- Organizes the input row: text field + send button.
- Adds padding and border to separate it from the chat.
input: {
flex: 1,
backgroundColor: '#f1f1f1',
borderRadius: 25,
paddingHorizontal: 15,
paddingVertical: 10,
fontSize: 16,
},
- Rounded, light-colored input box.
- Takes most of the row (flex: 1) for typing.
sendButton: {
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 25,
marginLeft: 10,
},
sendButtonText: {
color: '#fff',
fontWeight: 'bold',
fontSize: 16,
},
- Circular-style send button with bold white text
Final Result
You now have a working chat app with:
- Real-time messages powered by PieSocket
- Smooth UI with message alignment
- Random usernames and timestamps
Complete Code
The project is available on our GitHub : https://github.com/piehostHQ/react-native-chat
