React

React Chat App - Real Time

Introduction

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]

Prerequisites

To complete this tutorial, you will need:

Goal

To build a live chat application on React using @parse/react hook as an enabler for Live Query on Parse.

Step 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Create Nicknames
let NicknameA = new Parse.Object('Nickname');
NicknameA.set('name', 'SmartBoy22');
NicknameA = await NicknameA.save();
let NicknameB = new Parse.Object('Nickname');
NicknameB.set('name', 'CleverGirl23');
NicknameB = await NicknameB.save();

// Create Message linked to Nicknames
let Message = new Parse.Object('Message');
Message.set('text', 'Hi! How are you?');
Message.set('sender', NicknameA);
Message.set('receiver', NicknameA);
Message = await Message.save();
console.log('success');

Step 2 - Enabling Live Query

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.

React Back4App

The next thing to do is to create our chat app, which consists of two components, ChatSetup and LiveChat.

Step 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, { useState } from "react";
import "./App.css";
import { Button, Input } from "antd";
import Parse from "parse";
import { LiveChat } from "./LiveChat";

export const ChatSetup = () => {
  // State variables holding input values and results
  const [senderNicknameInput, setSenderNicknameInput] = useState("");
  const [senderNicknameId, setSenderNicknameId] = useState(null);
  const [receiverNicknameInput, setReceiverNicknameInput] = useState("");
  const [receiverNicknameId, setReceiverNicknameId] = useState(null);

  // Create or retrieve Nickname objects and start LiveChat component
  const startLiveChat = async () => {
    const senderNicknameName = senderNicknameInput;
    const receiverNicknameName = receiverNicknameInput;

    // Check if user informed both nicknames
    if (senderNicknameName === null || receiverNicknameName === null) {
      alert("Please inform both sender and receiver nicknames!");
      return false;
    }

    // Check if sender nickname already exists, if not create new parse object
    let senderNicknameObject = null;
    try {
      const senderParseQuery = new Parse.Query("Nickname");
      senderParseQuery.equalTo("name", senderNicknameName);
      const senderParseQueryResult = await senderParseQuery.first();
      if (
        senderParseQueryResult !== undefined &&
        senderParseQueryResult !== null
      ) {
        senderNicknameObject = senderParseQueryResult;
      } else {
        senderNicknameObject = new Parse.Object("Nickname");
        senderNicknameObject.set("name", senderNicknameName);
        senderNicknameObject = await senderNicknameObject.save();
      }
    } catch (error) {
      alert(error);
      return false;
    }

    // Check if receiver nickname already exists, if not create new parse object
    let receiverNicknameObject = null;
    try {
      const receiverParseQuery = new Parse.Query("Nickname");
      receiverParseQuery.equalTo("name", receiverNicknameName);
      const receiverParseQueryResult = await receiverParseQuery.first();
      if (
        receiverParseQueryResult !== undefined &&
        receiverParseQueryResult !== null
      ) {
        receiverNicknameObject = receiverParseQueryResult;
      } else {
        receiverNicknameObject = new Parse.Object("Nickname");
        receiverNicknameObject.set("name", receiverNicknameName);
        receiverNicknameObject = await receiverNicknameObject.save();
      }
    } catch (error) {
      alert(error);
      return false;
    }

    // Set nickname objects ids, so live chat component is instantiated
    setSenderNicknameId(senderNicknameObject.id);
    setReceiverNicknameId(receiverNicknameObject.id);
    return true;
  };

  return (
    <div>
      <div className="header">
        <img
          className="header_logo"
          alt="Back4App Logo"
          src={
            "https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png"
          }
        />
        <p className="header_text_bold">{"React on Back4App"}</p>
        <p className="header_text">{"Live query chat app"}</p>
      </div>
      <div className="container">
        {senderNicknameId === null && receiverNicknameId === null && (
          <div>
            <Input
              className="form_input"
              value={senderNicknameInput}
              onChange={(event) => setSenderNicknameInput(event.target.value)}
              placeholder={"Sender (Your) Nickname"}
              size="large"
            />
            <Input
              className="form_input"
              value={receiverNicknameInput}
              onChange={(event) => setReceiverNicknameInput(event.target.value)}
              placeholder={"Receiver (Their) Nickname"}
              size="large"
            />
            <Button
              type="primary"
              className="form_button"
              color={"#208AEC"}
              size={"large"}
              onClick={startLiveChat}
            >
              Start live chat
            </Button>
          </div>
        )}
        {senderNicknameId !== null && receiverNicknameId !== null && (
          <LiveChat
            senderNicknameName={senderNicknameInput}
            senderNicknameId={senderNicknameId}
            receiverNicknameName={receiverNicknameInput}
            receiverNicknameId={receiverNicknameId}
          />
        )}
      </div>
    </div>
  );
};

ChatSetup.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import React, { useState, FC, ReactElement } from "react";
import "./App.css";
import { Button, Input } from "antd";
import { LiveChat } from "./LiveChat";
import Parse from "parse";

export const ChatSetup: FC<{}> = (): ReactElement => {
  // State variables holding input values and results
  const [senderNicknameInput, setSenderNicknameInput] = useState("");
  const [senderNicknameId, setSenderNicknameId] = useState<string | null>(null);
  const [receiverNicknameInput, setReceiverNicknameInput] = useState("");
  const [receiverNicknameId, setReceiverNicknameId] = useState<string | null>(null);

  // Create or retrieve Nickname objects and start LiveChat component
  const startLiveChat = async (): Promise<Boolean> => {
    const senderNicknameName: string = senderNicknameInput;
    const receiverNicknameName: string = receiverNicknameInput;

    // Check if user informed both nicknames
    if (senderNicknameName === "" || receiverNicknameName === "") {
      alert("Please inform both sender and receiver nicknames!");
      return false;
    }

    // Check if sender nickname already exists, if not create new parse object
    let senderNicknameObject: Parse.Object | null = null;
    try {
      const senderParseQuery: Parse.Query = new Parse.Query("Nickname");
      senderParseQuery.equalTo("name", senderNicknameName);
      const senderParseQueryResult: Parse.Object | undefined = await senderParseQuery.first();
      if (
        senderParseQueryResult !== undefined
      ) {
        senderNicknameObject = senderParseQueryResult;
      } else {
        senderNicknameObject = new Parse.Object("Nickname");
        if (senderNicknameObject !== null) {
          senderNicknameObject.set("name", senderNicknameName);
          senderNicknameObject = await senderNicknameObject.save();
        }
      }
    } catch (error) {
      alert(error);
      return false;
    }

    // Check if receiver nickname already exists, if not create new parse object
    let receiverNicknameObject: Parse.Object | null = null;
    try {
      const receiverParseQuery: Parse.Query = new Parse.Query("Nickname");
      receiverParseQuery.equalTo("name", receiverNicknameName);
      const receiverParseQueryResult: Parse.Object | undefined = await receiverParseQuery.first();
      if (
        receiverParseQueryResult !== undefined
      ) {
        receiverNicknameObject = receiverParseQueryResult;
      } else {
        receiverNicknameObject = new Parse.Object("Nickname");
        if (receiverNicknameObject !== null) {
          receiverNicknameObject.set("name", receiverNicknameName);
          receiverNicknameObject = await receiverNicknameObject.save();
        }
      }
    } catch (error: any) {
      alert(error);
      return false;
    }

    // Set nickname objects ids, so live chat component is instantiated
    if (senderNicknameObject !== null && receiverNicknameObject !== null) {
      setSenderNicknameId(senderNicknameObject.id);
      setReceiverNicknameId(receiverNicknameObject.id);
    }
    return true;
  };

  return (
    <div>
      <div className="header">
        <img
          className="header_logo"
          alt="Back4App Logo"
          src={
            "https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png"
          }
        />
        <p className="header_text_bold">{"React on Back4App"}</p>
        <p className="header_text">{"Live query chat app"}</p>
      </div>
      <div className="container">
        {senderNicknameId === null && receiverNicknameId === null && (
          <div>
            <Input
              className="form_input"
              value={senderNicknameInput}
              onChange={(event) => setSenderNicknameInput(event.target.value)}
              placeholder={"Sender (Your) Nickname"}
              size="large"
            />
            <Input
              className="form_input"
              value={receiverNicknameInput}
              onChange={(event) => setReceiverNicknameInput(event.target.value)}
              placeholder={"Receiver (Their) Nickname"}
              size="large"
            />
            <Button
              type="primary"
              className="form_button"
              color={"#208AEC"}
              size={"large"}
              onClick={startLiveChat}
            >
              Start live chat
            </Button>
          </div>
        )}
        {senderNicknameId !== null && receiverNicknameId !== null && (
          <LiveChat
            senderNicknameName={senderNicknameInput}
            senderNicknameId={senderNicknameId}
            receiverNicknameName={receiverNicknameInput}
            receiverNicknameId={receiverNicknameId}
          />
        )}
      </div>
    </div>
  );
};

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.

Step 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import React, { useState } from "react";
import "./App.css";
import { Button, Input, Tooltip } from "antd";
import { SyncOutlined } from "@ant-design/icons";
import Parse from "parse";
import { useParseQuery } from "@parse/react";

export const LiveChat = (props) => {
  // State variable to hold message text input
  const [messageInput, setMessageInput] = useState("");

  // Create parse query for live querying using useParseQuery hook
  const parseQuery = new Parse.Query("Message");
  // Get messages that involve both nicknames
  parseQuery.containedIn("sender", [
    props.senderNicknameId,
    props.receiverNicknameId,
  ]);
  parseQuery.containedIn("receiver", [
    props.senderNicknameId,
    props.receiverNicknameId,
  ]);
  // Set results ordering
  parseQuery.ascending("createdAt");

  // Include nickname fields, to enable name getting on list
  parseQuery.includeAll();

  // Declare hook and variables to hold hook responses
  const { isLive, isLoading, isSyncing, results, count, error, reload } =
    useParseQuery(parseQuery, {
      enableLocalDatastore: true, // Enables cache in local datastore (default: true)
      enableLiveQuery: true, // Enables live query for real-time update (default: true)
    });

  // Message sender handler
  const sendMessage = async () => {
    try {
      const messageText = messageInput;

      // Get sender and receiver nickname Parse objects
      const senderNicknameObjectQuery = new Parse.Query("Nickname");
      senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);
      let senderNicknameObject = await senderNicknameObjectQuery.first();
      const receiverNicknameObjectQuery = new Parse.Query("Nickname");
      receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);
      let receiverNicknameObject = await receiverNicknameObjectQuery.first();

      // Create new Message object and save it
      let Message = new Parse.Object("Message");
      Message.set("text", messageText);
      Message.set("sender", senderNicknameObject);
      Message.set("receiver", receiverNicknameObject);
      Message.save();

      // Clear input
      setMessageInput("");
    } catch (error) {
      alert(error);
    }
  };

  // Helper to format createdAt value on Message
  const formatDateToTime = (date) => {
    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
  };

  return (
    <div>
      <div className="flex_between">
        <h2 class="list_heading">{`${props.senderNicknameName} sending, ${props.receiverNicknameName} receiving!`}</h2>
        <Tooltip title="Reload">
          <Button
            onClick={reload}
            type="primary"
            shape="circle"
            icon={<SyncOutlined />}
          />
        </Tooltip>
      </div>
      {results && (
        <div className="messages">
          {results
            .sort((a, b) => a.get("createdAt") > b.get("createdAt"))
            .map((result) => (
              <div
                key={result.id}
                className={
                  result.get("sender").id === props.senderNicknameId
                    ? "message_sent"
                    : "message_received"
                }
              >
                <p className="message_bubble">{result.get("text")}</p>
                <p className="message_time">
                  {formatDateToTime(result.get("createdAt"))}
                </p>
                <p className="message_name">
                  {result.get("sender").get("name")}
                </p>
              </div>
            ))}
        </div>
      )}
      <div className="new_message">
        <h2 className="new_message_title">New message</h2>
        <Input
          className="form_input"
          value={messageInput}
          onChange={(event) => setMessageInput(event.target.value)}
          placeholder={"Your message..."}
          size="large"
        />
        <Button
          type="primary"
          className="form_button"
          color={"#208AEC"}
          size={"large"}
          onClick={sendMessage}
        >
          Send message
        </Button>
      </div>
      <div>
        {isLoading && <p>{"Loading…"}</p>}
        {isSyncing && <p>{"Syncing…"}</p>}
        {isLive ? <p>{"Status: Live"}</p> : <p>{"Status: Offline"}</p>}
        {error && <p>{error.message}</p>}
        {count && <p>{`Count: ${count}`}</p>}
      </div>
    </div>
  );
};

LiveChat.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import React, { useState, FC, ReactElement } from "react";
import "./App.css";
import { Button, Input, Tooltip } from "antd";
import { SyncOutlined } from "@ant-design/icons";
import Parse from "parse";
import { useParseQuery } from  "@parse/react";

type LiveChatProps = {
  senderNicknameId: string,
  senderNicknameName: string,
  receiverNicknameId: string,
  receiverNicknameName: string,
}

export const LiveChat: FC<LiveChatProps> = (props: LiveChatProps): ReactElement => {
  const Parse = require("parse/dist/parse.min.js");
    // State variable to hold message text input
  const [messageInput, setMessageInput] = useState("");

  // Create parse query for live querying using useParseQuery hook
  const parseQuery: Parse.Query = new Parse.Query("Message");
  // Get messages that involve both nicknames
  parseQuery.containedIn("sender", [
    props.senderNicknameId,
    props.receiverNicknameId,
  ]);
  parseQuery.containedIn("receiver", [
    props.senderNicknameId,
    props.receiverNicknameId,
  ]);
  // Set results ordering
  parseQuery.ascending("createdAt");

  // Include nickname fields, to enable name getting on list
  parseQuery.includeAll();

  // Declare hook and variables to hold hook responses
  const { isLive, isLoading, isSyncing, results, count, error, reload } =
    useParseQuery(parseQuery, {
      enableLocalDatastore: true, // Enables cache in local datastore (default: true)
      enableLiveQuery: true, // Enables live query for real-time update (default: true)
    });

  // Message sender handler
  const sendMessage = async () => {
    try {
      const messageText: string = messageInput;

      // Get sender and receiver nickname Parse objects
      const senderNicknameObjectQuery: Parse.Query = new Parse.Query("Nickname");
      senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);
      let senderNicknameObject: Parse.Object | undefined = await senderNicknameObjectQuery.first();
      const receiverNicknameObjectQuery: Parse.Query = new Parse.Query("Nickname");
      receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);
      let receiverNicknameObject: Parse.Object | undefined = await receiverNicknameObjectQuery.first();

      // Create new Message object and save it
      let Message: Parse.Object = new Parse.Object("Message");
      Message.set("text", messageText);
      Message.set("sender", senderNicknameObject);
      Message.set("receiver", receiverNicknameObject);
      Message.save();

      // Clear input
      setMessageInput("");
    } catch (error: any) {
      alert(error);
    }
  };

  // Helper to format createdAt value on Message
  const formatDateToTime = (date: Date): string => {
    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
  };

  return (
    <div>
      <div className="flex_between">
        <h2 class="list_heading">{`${props.senderNicknameName} sending, ${props.receiverNicknameName} receiving!`}</h2>
        <Tooltip title="Reload">
          <Button
            onClick={reload}
            type="primary"
            shape="circle"
            icon={<SyncOutlined />}
          />
        </Tooltip>
      </div>
      {results && (
        <div className="messages">
          {results
            .sort((a, b) => a.get("createdAt") > b.get("createdAt"))
            .map((result) => (
              <div
                key={result.id}
                className={
                  result.get("sender").id === props.senderNicknameId
                    ? "message_sent"
                    : "message_received"
                }
              >
                <p className="message_bubble">{result.get("text")}</p>
                <p className="message_time">
                  {formatDateToTime(result.get("createdAt"))}
                </p>
                <p className="message_name">
                  {result.get("sender").get("name")}
                </p>
              </div>
            ))}
        </div>
      )}
      <div className="new_message">
        <h2 className="new_message_title">New message</h2>
        <Input
          className="form_input"
          value={messageInput}
          onChange={(event) => setMessageInput(event.target.value)}
          placeholder={"Your message..."}
          size="large"
        />
        <Button
          type="primary"
          className="form_button"
          color={"#208AEC"}
          size={"large"}
          onClick={sendMessage}
        >
          Send message
        </Button>
      </div>
      <div>
        {isLoading && <p>{"Loading…"}</p>}
        {isSyncing && <p>{"Syncing…"}</p>}
        {isLive ? <p>{"Status: Live"}</p> : <p>{"Status: Offline"}</p>}
        {error && <p>{error.message}</p>}
        {count && <p>{`Count: ${count}`}</p>}
      </div>
    </div>
  );
};

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.

App.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
@import '~antd/dist/antd.css';

html {
  box-sizing: border-box;
  outline: none;
  overflow: auto;
}

body {
  margin: 0;
  background-color: #fff;
}

*,
*:before,
*:after {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  font-weight: bold;
}

li {
  list-style: none;
}

p {
  margin: 0;
}

.flex_between {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.list_heading {
  font-weight: bold;
}

.App {
  text-align: center;
}

.container {
  width: 100%;
  max-width: 500px;
  margin: auto;
  padding: 20px 0;
  text-align: left;
}

.header {
  align-items: center;
  padding: 25px 0;
  background-color: #208AEC;
}

.header_logo {
  height: 55px;
  margin-bottom: 20px;
  object-fit: contain;
}

.header_text_bold {
  margin-bottom: 3px;
  color: rgba(255, 255, 255, 0.9);
  font-size: 16px;
  font-weight: bold;
}

.header_text {
  color: rgba(255, 255, 255, 0.9);
  font-size: 15px;
}

.heading {
  font-size: 22px;
}

.form_wrapper {
  margin-top: 20px;
  margin-bottom: 10px;
}

.form_input {
  margin-bottom: 20px;
}

.form_button {
  width: 100%;
}

.messages {
  margin-top: 25px;
}

.message_sent {
  position: relative;
  width: 50%;
  margin-left: auto;
}

.message_received {
  position: relative;
  width: 50%;
}

.message_bubble {
  padding: 12px;
  border-radius: 25px;
  background-color: rgba(0, 0, 0, 0.2);
}

.message_sent .message_bubble {
  background-color: #1E88E5;
  color: #fff;
}

.message_time {
  position: absolute;
  top: 35%;
  left: -62px;
  font-size: 13px;
  color: rgba(0, 0, 0, 0.35);
  transform: translateY(-50%);
}

.message_sent .message_time {
  left: initial;
  right: -62px;
}

.message_name {
  margin-top: 5px;
  color: rgba(0, 0, 0, 0.55);
  font-size: 13px;
  font-weight: 600;
}

.message_sent .message_name {
  text-align: right;
}

.new_message {
  padding-top: 15px;
  margin-top: 20px;
  margin-bottom: 10px;
  border-top: 1px solid rgba(0, 0, 0, 0.12);
}

.new_message_title {
  margin-bottom: 15px;
}

Step 5 - Testing the chat application

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from "react";
import "./App.css";
import { initializeParse } from "@parse/react";
import { ChatSetup } from "./ChatSetup";

// Your Parse initialization configuration goes here
// Note the live query URL instead of the regular server url
const PARSE_APPLICATION_ID = "YOUR_PARSE_APPLICATION_ID";
// const PARSE_SERVER_URL = "https://parseapi.back4app.com/";
const PARSE_LIVE_QUERY_URL = "https://YOUR_APP_NAME.b4a.io/";
const PARSE_JAVASCRIPT_KEY = "YOUR_PARSE_JAVASCRIPT_KEY";

// Initialize parse using @parse/react instead of regular parse JS SDK
initializeParse(
  PARSE_LIVE_QUERY_URL,
  PARSE_APPLICATION_ID,
  PARSE_JAVASCRIPT_KEY
);

function App() {
  return (
    <div className="App">
      <ChatSetup />
    </div>
  );
}

export default 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.

React Back4App

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.

React Back4App

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.