In this guide, we will keep exploring the Parse React hook in useful situations by creating a Slack Clone App. The App will have basic features such as signup/login, channels, and real-time chat. In addition, the React components highlight the usefulness of real-time notifications using Live Query, User management capabilities, and the flexibility to perform queries (relational).
At any moment you can quickly deploy a slack clone app on Vercel:
Clone or download this project via our GitHub repositories so you can run it along with the guide. Make sure to follow the instructions on the README file to successfully set it up in your local environment:
To build a Slack clone application on React using the @parse/react hook.
1 - Creating your Parse app from a template
This application comprises two database classes: Channel and Message, containing pointers to the Parse User class. Instead of creating the app and database classes from scratch, let’s clone an existing template at Back4App Database Hub. Click on the “Clone Database” button and proceed with logging in and creating your app. For more details on how to clone please check the clone App guide.
Now your App has the complete backend structure necessary to create your Slack Clone on Back4App.
2 - Enabling Live Query
Now that you have created the App and classes, you need to enable the live query(real-time). Go to your Back4App dashboard and navigate to App Settings > Server Settings > Server URL and Live Query. After activating your Back4App subdomain, you can then activate Live Query by selecting the classes Message and Channel. After that, save the changes.
The URL above is your Live Query URL make sure to copy it in order to properly initialize the Parse hook.
Let’s now dive into the core React components: ChannelList, MessageList, and Home.
3 - The List components
The ChannelList and MessageList components use the @parse/react hook to retrieve the User data through Live Query. They have the same pattern structure as in the React LiveChat guide. They are instantiated with initial parameters (retrieved via the props object) that dynamically compose their Live Query query. Take a look at their queries and how the classes are related to each other:
JavaScript
TypeScript
1// Note that Parse.Object coming from props need to be changed into pointers2// to be able to be used by Parse, since React changes the object structure3// when passing down parameters to child components45// channelList.js67const ownerQuery =newParse.Query("Channel");8 ownerQuery.equalTo("owner", props.currentUser.toPointer());9const membersQuery =newParse.Query("Channel");10 membersQuery.containedIn("members",[props.currentUser.toPointer()]);11// Creates the OR query12const parseQuery = Parse.Query.or(ownerQuery, membersQuery);13// Set results ordering14 parseQuery.ascending("name");15// Include all pointer fields16 parseQuery.includeAll();1718// messageList.js1920const parseQuery =newParse.Query("Message");21// Get messages that involve both nicknames22 parseQuery.equalTo("channel", props.currentChannel.toPointer());23// Set results ordering24 parseQuery.ascending("createdAt");25// Include nickname fields, to enable name getting on list26 parseQuery.includeAll();
These queries will be running every time there is a change in the classes data, so if another user in the channel sends a message, you will see it appearing there in real-time.
4 - The Home component
The Home component acts as the main application screen, in which the list components are conditionally rendered and instantiated when needed. You can find below the component code. Take a look at the functions for creating channels and inviting users to them.
Home.js
Home.tsx
1import React,{ useEffect, useState }from"react";2import"./App.css";3import{ Modal }from"antd";4import{ useHistory }from"react-router-dom";5import Parse from"parse";6import{ ChannelList }from"./ChannelList";7import{ MessageList }from"./MessageList";8import{ MemberList }from"./MemberList";910exportconstHome=()=>{11const history =useHistory();1213// State variables holding input values and flags14const[currentUser, setCurrentUser]=useState(null);15const[isCreateChannelModalVisible, setIsCreateChannelModalVisible]=16useState(false);17const[createChannelInput, setCreateChannelInput]=useState("");18const[currentChannel, setCurrentChannel]=useState(null);1920// This effect hook runs at every render and checks if there is a21// logged in user, redirecting to Login screen if needed22useEffect(()=>{23constcheckCurrentUser=async()=>{24try{25const user =await Parse.User.currentAsync();26if(user ===null|| user ===undefined){27 history.push("/");28}else{29if(currentUser ===null){30setCurrentUser(user);31}32}33returntrue;34}catch(_error){}35returnfalse;36};37checkCurrentUser();38});3940// Logout function41constdoLogout=async()=>{42// Logout43try{44await Parse.User.logOut();45// Force useEffect execution to redirect back to Login46setCurrentUser(null);47returntrue;48}catch(error){49alert(error);50returnfalse;51}52};5354// Makes modal visible55constshowCreateChannelModal=()=>{56setIsCreateChannelModalVisible(true);57};5859// Clear input and hide modal on cancel60consthandleCreateChannelModalCancel=()=>{61setCreateChannelInput("");62setIsCreateChannelModalVisible(false);63};6465// Creates a channel based on input from modal66constdoCreateChannel=async()=>{67const channelName = createChannelInput;6869if(channelName ===""){70alert("Please inform your new channel name!");71returnfalse;72}7374// Creates a new Parse.Object instance and set parameters75const Channel =newParse.Object("Channel");76 Channel.set("name", channelName);77 Channel.set("owner", currentUser);78// Members is an array of Parse.User objects, so .add() should be used to79// concatenate the value inside the array80 Channel.add("members", currentUser);8182// Clears input value and hide modal83setCreateChannelInput("");84setIsCreateChannelModalVisible(false);8586try{87// Save object on Parse server88const saveResult =await Channel.save();89// Set the created channel as the active channel,90// showing the message list for this channel91setCurrentChannel(saveResult);92alert(`Success on creating channel ${channelName}`);93returntrue;94}catch(error){95alert(error);96returnfalse;97}98};99100// Changes the active channel and shows the message list for it101// This is called using a callback in the ChannelList component102constdoSelectChannel=(channel)=>{103setCurrentChannel(null);104setCurrentChannel(channel);105};106107// Settings current channel to null hides the message list component108// This is called using a callback in the MessageList component109constdoClearCurrentChannel=()=>{110setCurrentChannel(null);111};112113return(114<div className="grid">115<div className="organizations">116<div className="organization">117<picture className="organization__picture">118<img
119 className="organization__img"120 src="https://scontent.fsqx1-1.fna.fbcdn.net/v/t1.6435-9/29136314_969639596535770_8356900498426560512_n.png?_nc_cat=103&ccb=1-5&_nc_sid=973b4a&_nc_ohc=D9actPSB8DUAX-zaA7F&_nc_ht=scontent.fsqx1-1.fna&oh=96679a09c5c4524f0a6c86110de697b6&oe=618525F9"121 alt=""122/>123</picture>124<p className="organization__title">Back4App</p>125</div>126<button className="button-inline" onClick={doLogout}>127<svg
128 className="button-inline__icon"129 xmlns="http://www.w3.org/2000/svg"130 width="24"131 height="24"132 viewBox="0 0 24 24"133 fill="none"134 stroke="currentColor"135 strokeWidth="2"136 strokeLinecap="round"137 strokeLinejoin="round"138>139<polyline points="9 10 4 15 9 20"></polyline>140<path d="M20 4v7a4 4 0 0 1-4 4H4"></path>141</svg>142<span className="button-inline__label">Log out</span>143</button>144</div>145<div className="channels">146{/* Action buttons (new channel and logout) */}147<div>148<Modal
149 title="Create new channel"150 visible={isCreateChannelModalVisible}151 onOk={doCreateChannel}152 onCancel={handleCreateChannelModalCancel}153 okText={"Create"}154>155<>156<label>{"Channel Name"}</label>157<input
158 type={"text"}159 value={createChannelInput}160 placeholder={"New Channel Name"}161 onChange={(event)=>setCreateChannelInput(event.target.value)}162></input>163</>164</Modal>165</div>166<div className="channels-header" onClick={showCreateChannelModal}>167<p className="channels-header__label">Channels</p>168<svg
169 className="channels-header__icon"170 xmlns="http://www.w3.org/2000/svg"171 height="24px"172 viewBox="0 0 24 24"173 width="24px"174 fill="#000000"175>176<path d="M0 0h24v24H0z" fill="none"/>177<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>178</svg>179</div>180{/* Channel list component, instantiated only when the user is successfully fetched */}181{currentUser !==null&&(182<ChannelList
183 currentUser={currentUser}184 selectChannelCallback={doSelectChannel}185/>186)}187</div>188<div className="messages">189{/* Message list component, instantiated only when there is a selected channel */}190{currentUser !==null&& currentChannel !==null&&(191<MessageList
192 currentUser={currentUser}193 currentChannel={currentChannel}194 closeChannelCallback={doClearCurrentChannel}195/>196)}197</div>198<div className="info">199{/* Member list component, instantiated only when there is a selected channel */}200{currentUser !==null&& currentChannel !==null&&(201<MemberList
202 currentUser={currentUser}203 currentChannel={currentChannel}204 closeChannelCallback={doClearCurrentChannel}205/>206)}207</div>208</div>209);210};
This approach of dynamically instantiating the Live Query components allows us to reuse them whenever the user changes the active channel, creates a new one, sends a message, etc. Here is how the complete App will look.
5 - Deploy on Vercel
At any time you can deploy the application on vercel by clicking on the link below:
Make sure you have your Application ID, Client Key and LiveQuery URL. For the keys you can go to App Settings -> Security & Keys and then copy. For the Live Query URL you can go to Step 2 and copy it.
Conclusion
At the end of this guide, you learned more about using the Parse React hook for live queries in Parse and how to use Back4App’s Database Hub.