In the last guide, you got to know more about the @parse/react helper library that quickly enables Parse Live Query support on your React application. The lib is written entirely in Typescript, on top of Parse Javascript SDK, and is currently on the Alpha version.
Now, in this guide, you will use the Parse React hook in a realistic situation creating a simplistic live chat application. This app is composed of two React components that highlight the usefulness of Parse’s Live Query and also show everything you need to know to create your complete live app.
Parse React Native is currently on the Alpha version. The lib is under testing, so we recommend proceeding with caution. Your feedback is very appreciated, so feel free to use the lib and send us your questions and first impressions by dropping an email to [email protected].
If you want to test/use the screen layout provided by this guide, you should set up the Ant Design library.
Goal
To build a live chat application on React using @parse/react hook as an enabler for Live Query on Parse.
1 - Understanding and creating the database classes
The chat application will be composed of two small database classes: Nickname and Message. Nickname only has a text field called name and will represent the users in the application. Message will hold any text message sent between two users, so it needs to have a text field called text and two object pointer fields called sender and receiver, both related to the Nickname class.
Run the following snippet on your Back4App dashboard’s Javascript console to create these classes and populate them with some samples.
Now that you have created the Nickname and Message classes, we need to enable them with live query capabilities. 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 and select which DB classes will be enabled to it. Make sure to select the new classes and save the changes.
The next thing to do is to create our chat app, which consists of two components, ChatSetup and LiveChat.
3 - Creating the Chat Setup component
This component is responsible for creating and setting up the sender and receiver Nickname objects while serving also as a container for the LiveChat component structure. The layout for the setup part has only two input fields for the nicknames and a button that triggers the setup function. Create a new file in your src directory called ChatSetup.js (or ChatSetup.tsx) and use the following code:
ChatSetup.js
ChatSetup.tsx
1import React,{ useState,FC, ReactElement }from"react";2import"./App.css";3import{ Button, Input }from"antd";4import{ LiveChat }from"./LiveChat";5import Parse from"parse";67exportconst ChatSetup:FC<{}>=(): ReactElement =>{8// State variables holding input values and results9const[senderNicknameInput, setSenderNicknameInput]=useState("");10const[senderNicknameId, setSenderNicknameId]=useState<string|null>(null);11const[receiverNicknameInput, setReceiverNicknameInput]=useState("");12const[receiverNicknameId, setReceiverNicknameId]=useState<string|null>(null);1314// Create or retrieve Nickname objects and start LiveChat component15const startLiveChat =async():Promise<Boolean>=>{16const senderNicknameName:string= senderNicknameInput;17const receiverNicknameName:string= receiverNicknameInput;1819// Check if user informed both nicknames20if(senderNicknameName ===""|| receiverNicknameName ===""){21alert("Please inform both sender and receiver nicknames!");22returnfalse;23}2425// Check if sender nickname already exists, if not create new parse object26let senderNicknameObject: Parse.Object |null=null;27try{28const senderParseQuery: Parse.Query =newParse.Query("Nickname");29 senderParseQuery.equalTo("name", senderNicknameName);30const senderParseQueryResult: Parse.Object |undefined=await senderParseQuery.first();31if(32 senderParseQueryResult !==undefined33){34 senderNicknameObject = senderParseQueryResult;35}else{36 senderNicknameObject =newParse.Object("Nickname");37if(senderNicknameObject !==null){38 senderNicknameObject.set("name", senderNicknameName);39 senderNicknameObject =await senderNicknameObject.save();40}41}42}catch(error){43alert(error);44returnfalse;45}4647// Check if receiver nickname already exists, if not create new parse object48let receiverNicknameObject: Parse.Object |null=null;49try{50const receiverParseQuery: Parse.Query =newParse.Query("Nickname");51 receiverParseQuery.equalTo("name", receiverNicknameName);52const receiverParseQueryResult: Parse.Object |undefined=await receiverParseQuery.first();53if(54 receiverParseQueryResult !==undefined55){56 receiverNicknameObject = receiverParseQueryResult;57}else{58 receiverNicknameObject =newParse.Object("Nickname");59if(receiverNicknameObject !==null){60 receiverNicknameObject.set("name", receiverNicknameName);61 receiverNicknameObject =await receiverNicknameObject.save();62}63}64}catch(error:any){65alert(error);66returnfalse;67}6869// Set nickname objects ids, so live chat component is instantiated70if(senderNicknameObject !==null&& receiverNicknameObject !==null){71setSenderNicknameId(senderNicknameObject.id);72setReceiverNicknameId(receiverNicknameObject.id);73}74returntrue;75};7677return(78<div>79<div className="header">80<img
81 className="header_logo"82 alt="Back4App Logo"83 src={84"https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png"85}86/>87<p className="header_text_bold">{"React on Back4App"}</p>88<p className="header_text">{"Live query chat app"}</p>89</div>90<div className="container">91{senderNicknameId ===null&& receiverNicknameId ===null&&(92<div>93<Input
94 className="form_input"95 value={senderNicknameInput}96 onChange={(event)=>setSenderNicknameInput(event.target.value)}97 placeholder={"Sender (Your) Nickname"}98 size="large"99/>100<Input
101 className="form_input"102 value={receiverNicknameInput}103 onChange={(event)=>setReceiverNicknameInput(event.target.value)}104 placeholder={"Receiver (Their) Nickname"}105 size="large"106/>107<Button
108 type="primary"109 className="form_button"110 color={"#208AEC"}111 size={"large"}112 onClick={startLiveChat}113>114 Start live chat
115</Button>116</div>117)}118{senderNicknameId !==null&& receiverNicknameId !==null&&(119<LiveChat
120 senderNicknameName={senderNicknameInput}121 senderNicknameId={senderNicknameId}122 receiverNicknameName={receiverNicknameInput}123 receiverNicknameId={receiverNicknameId}124/>125)}126</div>127</div>128);129};
Note that the LiveChat component is only initialized and rendered when the setup process is successful and all the state variables are properly set. Likewise, the setup inputs are hidden after the process and the child component layout is rendered.
4 - Creating the Live Chat component
The LiveChat component handles the exhibition and sending of the Messages between the two Nicknames passed as parameters on its initialization. It’s in this component that you will finally use the useParseQuery hook from @parse/react to set up the Live Query that will retrieve any Message object related to this chat instance. Create a new file in your src directory called LiveChat.js (or LiveChat.tsx) and insert the following code.
LiveChat.js
LiveChat.tsx
1import React,{ useState }from"react";2import"./App.css";3import{ Button, Input, Tooltip }from"antd";4import{ SyncOutlined }from"@ant-design/icons";5import Parse from"parse";6import{ useParseQuery }from"@parse/react";78exportconstLiveChat=(props)=>{9// State variable to hold message text input10const[messageInput, setMessageInput]=useState("");1112// Create parse query for live querying using useParseQuery hook13const parseQuery =newParse.Query("Message");14// Get messages that involve both nicknames15 parseQuery.containedIn("sender",[16 props.senderNicknameId,17 props.receiverNicknameId,18]);19 parseQuery.containedIn("receiver",[20 props.senderNicknameId,21 props.receiverNicknameId,22]);23// Set results ordering24 parseQuery.ascending("createdAt");2526// Include nickname fields, to enable name getting on list27 parseQuery.includeAll();2829// Declare hook and variables to hold hook responses30const{ isLive, isLoading, isSyncing, results, count, error, reload }=31useParseQuery(parseQuery,{32enableLocalDatastore:true,// Enables cache in local datastore (default: true)33enableLiveQuery:true,// Enables live query for real-time update (default: true)34});3536// Message sender handler37constsendMessage=async()=>{38try{39const messageText = messageInput;4041// Get sender and receiver nickname Parse objects42const senderNicknameObjectQuery =newParse.Query("Nickname");43 senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);44let senderNicknameObject =await senderNicknameObjectQuery.first();45const receiverNicknameObjectQuery =newParse.Query("Nickname");46 receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);47let receiverNicknameObject =await receiverNicknameObjectQuery.first();4849// Create new Message object and save it50let Message =newParse.Object("Message");51 Message.set("text", messageText);52 Message.set("sender", senderNicknameObject);53 Message.set("receiver", receiverNicknameObject);54 Message.save();5556// Clear input57setMessageInput("");58}catch(error){59alert(error);60}61};6263// Helper to format createdAt value on Message64constformatDateToTime=(date)=>{65return`${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;66};6768return(69<div>70<div className="flex_between">71<h2 class="list_heading">{`${props.senderNicknameName} sending, ${props.receiverNicknameName} receiving!`}</h2>72<Tooltip title="Reload">73<Button
74 onClick={reload}75 type="primary"76 shape="circle"77 icon={<SyncOutlined />}78/>79</Tooltip>80</div>81{results &&(82<div className="messages">83{results
84.sort((a, b)=> a.get("createdAt")> b.get("createdAt"))85.map((result)=>(86<div
87 key={result.id}88 className={89 result.get("sender").id === props.senderNicknameId
90?"message_sent"91:"message_received"92}93>94<p className="message_bubble">{result.get("text")}</p>95<p className="message_time">96{formatDateToTime(result.get("createdAt"))}97</p>98<p className="message_name">99{result.get("sender").get("name")}100</p>101</div>102))}103</div>104)}105<div className="new_message">106<h2 className="new_message_title">New message</h2>107<Input
108 className="form_input"109 value={messageInput}110 onChange={(event)=>setMessageInput(event.target.value)}111 placeholder={"Your message..."}112 size="large"113/>114<Button
115 type="primary"116 className="form_button"117 color={"#208AEC"}118 size={"large"}119 onClick={sendMessage}120>121 Send message
122</Button>123</div>124<div>125{isLoading &&<p>{"Loading…"}</p>}126{isSyncing &&<p>{"Syncing…"}</p>}127{isLive ?<p>{"Status: Live"}</p>:<p>{"Status: Offline"}</p>}128{error &&<p>{error.message}</p>}129{count &&<p>{`Count: ${count}`}</p>}130</div>131</div>132);133};
Let’s break down this component structure into four parts, so you can better understand its layout:
At the top we have the message’s Parse.Query and Parse React hook setup. Here you can see how the props parameters are used to enable the query to retrieve the messages that we want;
After that, you have the sendMessage function, which will create a new Message object relating it to the Nickname used in this chat instance. There is also a helper function for formatting the messages date value;
Now, inside the JSX code, we have the status flags that are related to the Parse React hook variables and also the connection reload button;
Lastly, you can see the Message list in which the rendered list items style is dictated by its sender value. At the bottom, we have the message sending part, with a simple text input and a button.
Finally, add these classes to your App.css file if you want to fully render the components layout, and let’s proceed to test our app.
Go ahead and test the live chat app by declaring and calling the ChatSetup component on your App.js (or App.tsx) JSX code. Here is an example of how you could do that:
App.js or App.tsx
1import React from"react";2import"./App.css";3import{ initializeParse }from"@parse/react";4import{ ChatSetup }from"./ChatSetup";56// Your Parse initialization configuration goes here7// Note the live query URL instead of the regular server url8constPARSE_APPLICATION_ID="YOUR_PARSE_APPLICATION_ID";9// const PARSE_SERVER_URL = "https://parseapi.back4app.com/";10constPARSE_LIVE_QUERY_URL="https://YOUR_APP_NAME.b4a.io/";11constPARSE_JAVASCRIPT_KEY="YOUR_PARSE_JAVASCRIPT_KEY";1213// Initialize parse using @parse/react instead of regular parse JS SDK14initializeParse(15PARSE_LIVE_QUERY_URL,16PARSE_APPLICATION_ID,17PARSE_JAVASCRIPT_KEY18);1920functionApp(){21return(22<div className="App">23<ChatSetup />24</div>25);26}2728exportdefault App;
Start your app by running yarn start on your console. You should now be presented with the following screen, in which you need to inform the sending and receiving nicknames to begin chatting.
To better see how the app and live query are working, open the same app on two different browser windows and set them side by side. Immediately after sending a message in a window, you should see it pop on the other if the nicknames match and the connection is live.
Conclusion
At the end of this guide, you learned how to use the Parse React hook for live queries in Parse in a realistic application example.