---
title: 리액트 실시간 채팅 앱 개발 및 Parse React 훅 활용
slug: docs-kr/react/real-time/react-chat-app
description: 리액트에서 @parse/react 훅과 Parse Live Query를 사용한 실시간 채팅 앱 개발 가이드.
image: https://www.back4app.com/images/logo-social.png
createdAt: 2024-03-19T18:26:56.427Z
---

# 리액트 채팅 앱 - 실시간

## 소개

지난 가이드에서는 <font color="#2166ae">`@parse/react` </font> 헬퍼 라이브러리에 대해 더 알게 되었습니다. 이 라이브러리는 리액트 애플리케이션에서 Parse Live Query 지원을 빠르게 활성화합니다. 이 라이브러리는 전적으로 Typescript로 작성되었으며, [Parse Javascript SDK](https://www.npmjs.com/package/parse), 현재 알파 버전입니다.

이제 이 가이드에서는 Parse React 훅을 사용하여 간단한 실시간 채팅 애플리케이션을 만드는 현실적인 상황을 다룰 것입니다. 이 앱은 Parse의 Live Query의 유용성을 강조하는 두 개의 리액트 컴포넌트로 구성되어 있으며, 완전한 실시간 앱을 만드는 데 필요한 모든 것을 보여줍니다.

:::hint{type="danger"}
Parse React Native는 현재 알파 버전입니다. 이 라이브러리는 테스트 중이므로 주의해서 진행하는 것이 좋습니다. 여러분의 피드백은 매우 감사하므로, 이 라이브러리를 사용하고 질문이나 첫 인상을 community\@back4app.com으로 이메일로 보내주시면 됩니다.
:::

## 전제 조건

:::hint{type="info"}
이 튜토리얼을 완료하려면 다음이 필요합니다:


- 생성된 리액트 앱과 [Back4App에 연결된](https://www.back4app.com/docs/react/quickstart).
- 이전 가이드를 완료하여 [Parse React 훅과 Live Query에 대한 더 나은 이해](https://www.back4app.com/docs/react/ral-time/react-real-time-hoook).
- 이 가이드에서 제공하는 화면 레이아웃을 테스트/사용하려면 [<font color="#2166ae">`Ant Design`</font> 라이브러리를 설정해야 합니다.](https://ant.design/docs/react/introduce)
:::

## 목표

리액트에서 <font color="#2166ae">`@parse/react`</font> 훅을 사용하여 실시간 채팅 애플리케이션을 구축하는 것입니다.

## 1 - 데이터베이스 클래스 이해 및 생성

채팅 애플리케이션은 두 개의 작은 데이터베이스 클래스로 구성됩니다: <font color="#2166ae">`닉네임`</font> 및 <font color="#2166ae">`메시지`</font>. <font color="#2166ae">`닉네임`</font>은 <font color="#2166ae">`이름` </font>이라는 텍스트 필드만 가지고 있으며, 애플리케이션의 사용자들을 나타냅니다. <font color="#2166ae">`메시지`</font>는 두 사용자 간에 전송된 모든 텍스트 메시지를 보유하므로, <font color="#2166ae">`텍스트`</font>이라는 텍스트 필드와 <font color="#2166ae">`보낸 사람`</font> 및 <font color="#2166ae">`받는 사람`</font>, 두 개의 객체 포인터 필드가 필요합니다. 이들은 모두 <font color="#2166ae">`닉네임`</font> 클래스와 관련이 있습니다.

다음 스니펫을 Back4App 대시보드의 Javascript 콘솔에서 실행하여 이러한 클래스를 생성하고 샘플로 채워주세요.

```javascript
1	// Create Nicknames
2	let NicknameA = new Parse.Object('Nickname');
3	NicknameA.set('name', 'SmartBoy22');
4	NicknameA = await NicknameA.save();
5	let NicknameB = new Parse.Object('Nickname');
6	NicknameB.set('name', 'CleverGirl23');
7	NicknameB = await NicknameB.save();
8	
9	// Create Message linked to Nicknames
10	let Message = new Parse.Object('Message');
11	Message.set('text', 'Hi! How are you?');
12	Message.set('sender', NicknameA);
13	Message.set('receiver', NicknameA);
14	Message = await Message.save();
15	console.log('success');
```

## 2 - 실시간 쿼리 활성화

이제 <font color="#2166ae">`닉네임` </font>과 <font color="#2166ae">`메시지` </font>클래스를 생성했으므로, 실시간 쿼리 기능을 활성화해야 합니다. Back4App 대시보드로 이동하여 <font color="#2166ae">`앱 설정`</font> ><font color="#2166ae"> `서버 설정`</font> > <font color="#2166ae">`서버 URL 및 실시간 쿼리`</font>. Back4App 서브도메인을 활성화한 후, 실시간 쿼리를 활성화하고 어떤 DB 클래스를 활성화할지 선택할 수 있습니다. 새 클래스를 선택하고 변경 사항을 저장하는 것을 잊지 마세요.

![](https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/t7sc31kFzneS0eVyTZLU6_image.png)

다음으로 할 일은 두 개의 구성 요소로 구성된 채팅 앱을 만드는 것입니다. <font color="#2166ae">`ChatSetup` </font>과 <font color="#2166ae">`LiveChat`</font>입니다.

## 3 - 채팅 설정 구성 요소 만들기

이 구성 요소는 발신자와 수신자 <font color="#2166ae">`Nickname`</font> 객체를 생성하고 설정하는 역할을 하며, <font color="#2166ae">`LiveChat` </font> 구성 요소 구조의 컨테이너 역할도 합니다. 설정 부분의 레이아웃은 닉네임을 위한 두 개의 입력 필드와 설정 기능을 트리거하는 버튼만 있습니다. <font color="#2166ae">`src` </font> 디렉토리에 <font color="#2166ae">`ChatSetup.js`</font>라는 새 파일을 만들고 (또는 <font color="#2166ae">`ChatSetup.tsx`</font>) 다음 코드를 사용하세요:

:::CodeblockTabs
ChatSetup.js

```javascript
1	import React, { useState } from "react";
2	import "./App.css";
3	import { Button, Input } from "antd";
4	import Parse from "parse";
5	import { LiveChat } from "./LiveChat";
6	
7	export const ChatSetup = () => {
8	  // State variables holding input values and results
9	  const [senderNicknameInput, setSenderNicknameInput] = useState("");
10	  const [senderNicknameId, setSenderNicknameId] = useState(null);
11	  const [receiverNicknameInput, setReceiverNicknameInput] = useState("");
12	  const [receiverNicknameId, setReceiverNicknameId] = useState(null);
13	
14	  // Create or retrieve Nickname objects and start LiveChat component
15	  const startLiveChat = async () => {
16	    const senderNicknameName = senderNicknameInput;
17	    const receiverNicknameName = receiverNicknameInput;
18	
19	    // Check if user informed both nicknames
20	    if (senderNicknameName === null || receiverNicknameName === null) {
21	      alert("Please inform both sender and receiver nicknames!");
22	      return false;
23	    }
24	
25	    // Check if sender nickname already exists, if not create new parse object
26	    let senderNicknameObject = null;
27	    try {
28	      const senderParseQuery = new Parse.Query("Nickname");
29	      senderParseQuery.equalTo("name", senderNicknameName);
30	      const senderParseQueryResult = await senderParseQuery.first();
31	      if (
32	        senderParseQueryResult !== undefined &&
33	        senderParseQueryResult !== null
34	      ) {
35	        senderNicknameObject = senderParseQueryResult;
36	      } else {
37	        senderNicknameObject = new Parse.Object("Nickname");
38	        senderNicknameObject.set("name", senderNicknameName);
39	        senderNicknameObject = await senderNicknameObject.save();
40	      }
41	    } catch (error) {
42	      alert(error);
43	      return false;
44	    }
45	
46	    // Check if receiver nickname already exists, if not create new parse object
47	    let receiverNicknameObject = null;
48	    try {
49	      const receiverParseQuery = new Parse.Query("Nickname");
50	      receiverParseQuery.equalTo("name", receiverNicknameName);
51	      const receiverParseQueryResult = await receiverParseQuery.first();
52	      if (
53	        receiverParseQueryResult !== undefined &&
54	        receiverParseQueryResult !== null
55	      ) {
56	        receiverNicknameObject = receiverParseQueryResult;
57	      } else {
58	        receiverNicknameObject = new Parse.Object("Nickname");
59	        receiverNicknameObject.set("name", receiverNicknameName);
60	        receiverNicknameObject = await receiverNicknameObject.save();
61	      }
62	    } catch (error) {
63	      alert(error);
64	      return false;
65	    }
66	
67	    // Set nickname objects ids, so live chat component is instantiated
68	    setSenderNicknameId(senderNicknameObject.id);
69	    setReceiverNicknameId(receiverNicknameObject.id);
70	    return true;
71	  };
72	
73	  return (
74	    <div>
75	      <div className="header">
76	        <img
77	          className="header_logo"
78	          alt="Back4App Logo"
79	          src={
80	            "https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png"
81	          }
82	        />
83	        <p className="header_text_bold">{"React on Back4App"}</p>
84	        <p className="header_text">{"Live query chat app"}</p>
85	      </div>
86	      <div className="container">
87	        {senderNicknameId === null && receiverNicknameId === null && (
88	          <div>
89	            <Input
90	              className="form_input"
91	              value={senderNicknameInput}
92	              onChange={(event) => setSenderNicknameInput(event.target.value)}
93	              placeholder={"Sender (Your) Nickname"}
94	              size="large"
95	            />
96	            <Input
97	              className="form_input"
98	              value={receiverNicknameInput}
99	              onChange={(event) => setReceiverNicknameInput(event.target.value)}
100	              placeholder={"Receiver (Their) Nickname"}
101	              size="large"
102	            />
103	            <Button
104	              type="primary"
105	              className="form_button"
106	              color={"#208AEC"}
107	              size={"large"}
108	              onClick={startLiveChat}
109	            >
110	              Start live chat
111	            </Button>
112	          </div>
113	        )}
114	        {senderNicknameId !== null && receiverNicknameId !== null && (
115	          <LiveChat
116	            senderNicknameName={senderNicknameInput}
117	            senderNicknameId={senderNicknameId}
118	            receiverNicknameName={receiverNicknameInput}
119	            receiverNicknameId={receiverNicknameId}
120	          />
121	        )}
122	      </div>
123	    </div>
124	  );
125	};
```

ChatSetup.tsx

```typescript
1	import React, { useState, FC, ReactElement } from "react";
2	import "./App.css";
3	import { Button, Input } from "antd";
4	import { LiveChat } from "./LiveChat";
5	import Parse from "parse";
6	
7	export const ChatSetup: FC<{}> = (): ReactElement => {
8	  // State variables holding input values and results
9	  const [senderNicknameInput, setSenderNicknameInput] = useState("");
10	  const [senderNicknameId, setSenderNicknameId] = useState<string | null>(null);
11	  const [receiverNicknameInput, setReceiverNicknameInput] = useState("");
12	  const [receiverNicknameId, setReceiverNicknameId] = useState<string | null>(null);
13	
14	  // Create or retrieve Nickname objects and start LiveChat component
15	  const startLiveChat = async (): Promise<Boolean> => {
16	    const senderNicknameName: string = senderNicknameInput;
17	    const receiverNicknameName: string = receiverNicknameInput;
18	
19	    // Check if user informed both nicknames
20	    if (senderNicknameName === "" || receiverNicknameName === "") {
21	      alert("Please inform both sender and receiver nicknames!");
22	      return false;
23	    }
24	
25	    // Check if sender nickname already exists, if not create new parse object
26	    let senderNicknameObject: Parse.Object | null = null;
27	    try {
28	      const senderParseQuery: Parse.Query = new Parse.Query("Nickname");
29	      senderParseQuery.equalTo("name", senderNicknameName);
30	      const senderParseQueryResult: Parse.Object | undefined = await senderParseQuery.first();
31	      if (
32	        senderParseQueryResult !== undefined
33	      ) {
34	        senderNicknameObject = senderParseQueryResult;
35	      } else {
36	        senderNicknameObject = new Parse.Object("Nickname");
37	        if (senderNicknameObject !== null) {
38	          senderNicknameObject.set("name", senderNicknameName);
39	          senderNicknameObject = await senderNicknameObject.save();
40	        }
41	      }
42	    } catch (error) {
43	      alert(error);
44	      return false;
45	    }
46	
47	    // Check if receiver nickname already exists, if not create new parse object
48	    let receiverNicknameObject: Parse.Object | null = null;
49	    try {
50	      const receiverParseQuery: Parse.Query = new Parse.Query("Nickname");
51	      receiverParseQuery.equalTo("name", receiverNicknameName);
52	      const receiverParseQueryResult: Parse.Object | undefined = await receiverParseQuery.first();
53	      if (
54	        receiverParseQueryResult !== undefined
55	      ) {
56	        receiverNicknameObject = receiverParseQueryResult;
57	      } else {
58	        receiverNicknameObject = new Parse.Object("Nickname");
59	        if (receiverNicknameObject !== null) {
60	          receiverNicknameObject.set("name", receiverNicknameName);
61	          receiverNicknameObject = await receiverNicknameObject.save();
62	        }
63	      }
64	    } catch (error: any) {
65	      alert(error);
66	      return false;
67	    }
68	
69	    // Set nickname objects ids, so live chat component is instantiated
70	    if (senderNicknameObject !== null && receiverNicknameObject !== null) {
71	      setSenderNicknameId(senderNicknameObject.id);
72	      setReceiverNicknameId(receiverNicknameObject.id);
73	    }
74	    return true;
75	  };
76	
77	  return (
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	};
```
:::

다음 사항에 유의하십시오. <font color="#2166ae">`LiveChat`</font> 구성 요소는 설정 프로세스가 성공적으로 완료되고 모든 상태 변수가 올바르게 설정되었을 때만 초기화되고 렌더링됩니다. 마찬가지로, 설정 입력은 프로세스 후에 숨겨지며 자식 구성 요소 레이아웃이 렌더링됩니다.

## 4 - 라이브 채팅 구성 요소 만들기

이 <font color="#2166ae">`LiveChat`</font> 구성 요소는 <font color="#2166ae">`메시지`</font>의 전시 및 전송을 처리합니다. 이 메시지는 두 개의 <font color="#2166ae">`닉네임`</font>으로 초기화 시 매개변수로 전달됩니다. 이 구성 요소에서 <font color="#2166ae">`useParseQuery`</font> 훅을 사용하여 <font color="#2166ae">`@parse/react`</font>에서 Live Query를 설정하여 이 채팅 인스턴스와 관련된 <font color="#2166ae">`메시지`</font> 객체를 검색합니다. <font color="#2166ae">`src`</font> 디렉토리에 다음과 같은 이름의 새 파일을 만드세요.<font color="#2166ae"> `LiveChat.js`</font> (또는 <font color="#2166ae">`LiveChat.tsx`</font>) 그리고 다음 코드를 삽입하세요.

:::CodeblockTabs
LiveChat.js

```javascript
1	import React, { useState } from "react";
2	import "./App.css";
3	import { Button, Input, Tooltip } from "antd";
4	import { SyncOutlined } from "@ant-design/icons";
5	import Parse from "parse";
6	import { useParseQuery } from "@parse/react";
7	
8	export const LiveChat = (props) => {
9	  // State variable to hold message text input
10	  const [messageInput, setMessageInput] = useState("");
11	
12	  // Create parse query for live querying using useParseQuery hook
13	  const parseQuery = new Parse.Query("Message");
14	  // Get messages that involve both nicknames
15	  parseQuery.containedIn("sender", [
16	    props.senderNicknameId,
17	    props.receiverNicknameId,
18	  ]);
19	  parseQuery.containedIn("receiver", [
20	    props.senderNicknameId,
21	    props.receiverNicknameId,
22	  ]);
23	  // Set results ordering
24	  parseQuery.ascending("createdAt");
25	
26	  // Include nickname fields, to enable name getting on list
27	  parseQuery.includeAll();
28	
29	  // Declare hook and variables to hold hook responses
30	  const { isLive, isLoading, isSyncing, results, count, error, reload } =
31	    useParseQuery(parseQuery, {
32	      enableLocalDatastore: true, // Enables cache in local datastore (default: true)
33	      enableLiveQuery: true, // Enables live query for real-time update (default: true)
34	    });
35	
36	  // Message sender handler
37	  const sendMessage = async () => {
38	    try {
39	      const messageText = messageInput;
40	
41	      // Get sender and receiver nickname Parse objects
42	      const senderNicknameObjectQuery = new Parse.Query("Nickname");
43	      senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);
44	      let senderNicknameObject = await senderNicknameObjectQuery.first();
45	      const receiverNicknameObjectQuery = new Parse.Query("Nickname");
46	      receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);
47	      let receiverNicknameObject = await receiverNicknameObjectQuery.first();
48	
49	      // Create new Message object and save it
50	      let Message = new Parse.Object("Message");
51	      Message.set("text", messageText);
52	      Message.set("sender", senderNicknameObject);
53	      Message.set("receiver", receiverNicknameObject);
54	      Message.save();
55	
56	      // Clear input
57	      setMessageInput("");
58	    } catch (error) {
59	      alert(error);
60	    }
61	  };
62	
63	  // Helper to format createdAt value on Message
64	  const formatDateToTime = (date) => {
65	    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
66	  };
67	
68	  return (
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	};
```

LiveChat.tsx

```typescript
1	import React, { useState, FC, ReactElement } from "react";
2	import "./App.css";
3	import { Button, Input, Tooltip } from "antd";
4	import { SyncOutlined } from "@ant-design/icons";
5	import Parse from "parse";
6	import { useParseQuery } from  "@parse/react";
7	
8	type LiveChatProps = {
9	  senderNicknameId: string,
10	  senderNicknameName: string,
11	  receiverNicknameId: string,
12	  receiverNicknameName: string,
13	}
14	
15	export const LiveChat: FC<LiveChatProps> = (props: LiveChatProps): ReactElement => {
16	  const Parse = require("parse/dist/parse.min.js");
17	    // State variable to hold message text input
18	  const [messageInput, setMessageInput] = useState("");
19	
20	  // Create parse query for live querying using useParseQuery hook
21	  const parseQuery: Parse.Query = new Parse.Query("Message");
22	  // Get messages that involve both nicknames
23	  parseQuery.containedIn("sender", [
24	    props.senderNicknameId,
25	    props.receiverNicknameId,
26	  ]);
27	  parseQuery.containedIn("receiver", [
28	    props.senderNicknameId,
29	    props.receiverNicknameId,
30	  ]);
31	  // Set results ordering
32	  parseQuery.ascending("createdAt");
33	
34	  // Include nickname fields, to enable name getting on list
35	  parseQuery.includeAll();
36	
37	  // Declare hook and variables to hold hook responses
38	  const { isLive, isLoading, isSyncing, results, count, error, reload } =
39	    useParseQuery(parseQuery, {
40	      enableLocalDatastore: true, // Enables cache in local datastore (default: true)
41	      enableLiveQuery: true, // Enables live query for real-time update (default: true)
42	    });
43	
44	  // Message sender handler
45	  const sendMessage = async () => {
46	    try {
47	      const messageText: string = messageInput;
48	
49	      // Get sender and receiver nickname Parse objects
50	      const senderNicknameObjectQuery: Parse.Query = new Parse.Query("Nickname");
51	      senderNicknameObjectQuery.equalTo("objectId", props.senderNicknameId);
52	      let senderNicknameObject: Parse.Object | undefined = await senderNicknameObjectQuery.first();
53	      const receiverNicknameObjectQuery: Parse.Query = new Parse.Query("Nickname");
54	      receiverNicknameObjectQuery.equalTo("objectId", props.receiverNicknameId);
55	      let receiverNicknameObject: Parse.Object | undefined = await receiverNicknameObjectQuery.first();
56	
57	      // Create new Message object and save it
58	      let Message: Parse.Object = new Parse.Object("Message");
59	      Message.set("text", messageText);
60	      Message.set("sender", senderNicknameObject);
61	      Message.set("receiver", receiverNicknameObject);
62	      Message.save();
63	
64	      // Clear input
65	      setMessageInput("");
66	    } catch (error: any) {
67	      alert(error);
68	    }
69	  };
70	
71	  // Helper to format createdAt value on Message
72	  const formatDateToTime = (date: Date): string => {
73	    return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
74	  };
75	
76	  return (
77	    <div>
78	      <div className="flex_between">
79	        <h2 class="list_heading">{`${props.senderNicknameName} sending, ${props.receiverNicknameName} receiving!`}</h2>
80	        <Tooltip title="Reload">
81	          <Button
82	            onClick={reload}
83	            type="primary"
84	            shape="circle"
85	            icon={<SyncOutlined />}
86	          />
87	        </Tooltip>
88	      </div>
89	      {results && (
90	        <div className="messages">
91	          {results
92	            .sort((a, b) => a.get("createdAt") > b.get("createdAt"))
93	            .map((result) => (
94	              <div
95	                key={result.id}
96	                className={
97	                  result.get("sender").id === props.senderNicknameId
98	                    ? "message_sent"
99	                    : "message_received"
100	                }
101	              >
102	                <p className="message_bubble">{result.get("text")}</p>
103	                <p className="message_time">
104	                  {formatDateToTime(result.get("createdAt"))}
105	                </p>
106	                <p className="message_name">
107	                  {result.get("sender").get("name")}
108	                </p>
109	              </div>
110	            ))}
111	        </div>
112	      )}
113	      <div className="new_message">
114	        <h2 className="new_message_title">New message</h2>
115	        <Input
116	          className="form_input"
117	          value={messageInput}
118	          onChange={(event) => setMessageInput(event.target.value)}
119	          placeholder={"Your message..."}
120	          size="large"
121	        />
122	        <Button
123	          type="primary"
124	          className="form_button"
125	          color={"#208AEC"}
126	          size={"large"}
127	          onClick={sendMessage}
128	        >
129	          Send message
130	        </Button>
131	      </div>
132	      <div>
133	        {isLoading && <p>{"Loading…"}</p>}
134	        {isSyncing && <p>{"Syncing…"}</p>}
135	        {isLive ? <p>{"Status: Live"}</p> : <p>{"Status: Offline"}</p>}
136	        {error && <p>{error.message}</p>}
137	        {count && <p>{`Count: ${count}`}</p>}
138	      </div>
139	    </div>
140	  );
141	};
```
:::

이 구성 요소 구조를 네 부분으로 나누어 레이아웃을 더 잘 이해할 수 있도록 하겠습니다:


- 맨 위에는 메시지의 <font color="#2166ae">`Parse.Query`</font> 및 Parse React 훅 설정이 있습니다. 여기에서 <font color="#2166ae">`props`</font> 매개변수가 쿼리가 우리가 원하는 메시지를 검색할 수 있도록 하는 방법을 볼 수 있습니다;
- 그 후에 <font color="#2166ae">`sendMessage`</font> 함수가 있으며, 이는 새로운 <font color="#2166ae">`Message`</font> 객체를 생성하여 이 채팅 인스턴스에서 사용되는 <font color="#2166ae">`Nickname` </font>과 관련이 있습니다. 메시지 날짜 값을 포맷하는 도우미 함수도 있습니다;
- 이제 JSX 코드 안에는 Parse React 훅 변수와 관련된 상태 플래그와 연결 재로드 버튼이 있습니다;
- 마지막으로, <font color="#2166ae">`Message`</font> 목록을 볼 수 있으며, 렌더링된 목록 항목의 스타일은 발신자 값에 의해 결정됩니다. 하단에는 간단한 텍스트 입력 및 버튼이 있는 메시지 전송 부분이 있습니다.

마지막으로, 완전히 구성 요소 레이아웃을 렌더링하려면 이러한 클래스를 <font color="#2166ae">`App.css`</font> 파일에 추가하고, 앱을 테스트해 보겠습니다.

:::CodeblockTabs
App.css

```css
1	@import '~antd/dist/antd.css';
2	
3	html {
4	  box-sizing: border-box;
5	  outline: none;
6	  overflow: auto;
7	}
8	
9	body {
10	  margin: 0;
11	  background-color: #fff;
12	}
13	
14	*,
15	*:before,
16	*:after {
17	  margin: 0;
18	  padding: 0;
19	  box-sizing: inherit;
20	}
21	
22	h1,
23	h2,
24	h3,
25	h4,
26	h5,
27	h6 {
28	  margin: 0;
29	  font-weight: bold;
30	}
31	
32	li {
33	  list-style: none;
34	}
35	
36	p {
37	  margin: 0;
38	}
39	
40	.flex_between {
41	  display: flex;
42	  align-items: center;
43	  justify-content: space-between;
44	}
45	
46	.list_heading {
47	  font-weight: bold;
48	}
49	
50	.App {
51	  text-align: center;
52	}
53	
54	.container {
55	  width: 100%;
56	  max-width: 500px;
57	  margin: auto;
58	  padding: 20px 0;
59	  text-align: left;
60	}
61	
62	.header {
63	  align-items: center;
64	  padding: 25px 0;
65	  background-color: #208AEC;
66	}
67	
68	.header_logo {
69	  height: 55px;
70	  margin-bottom: 20px;
71	  object-fit: contain;
72	}
73	
74	.header_text_bold {
75	  margin-bottom: 3px;
76	  color: rgba(255, 255, 255, 0.9);
77	  font-size: 16px;
78	  font-weight: bold;
79	}
80	
81	.header_text {
82	  color: rgba(255, 255, 255, 0.9);
83	  font-size: 15px;
84	}
85	
86	.heading {
87	  font-size: 22px;
88	}
89	
90	.form_wrapper {
91	  margin-top: 20px;
92	  margin-bottom: 10px;
93	}
94	
95	.form_input {
96	  margin-bottom: 20px;
97	}
98	
99	.form_button {
100	  width: 100%;
101	}
102	
103	.messages {
104	  margin-top: 25px;
105	}
106	
107	.message_sent {
108	  position: relative;
109	  width: 50%;
110	  margin-left: auto;
111	}
112	
113	.message_received {
114	  position: relative;
115	  width: 50%;
116	}
117	
118	.message_bubble {
119	  padding: 12px;
120	  border-radius: 25px;
121	  background-color: rgba(0, 0, 0, 0.2);
122	}
123	
124	.message_sent .message_bubble {
125	  background-color: #1E88E5;
126	  color: #fff;
127	}
128	
129	.message_time {
130	  position: absolute;
131	  top: 35%;
132	  left: -62px;
133	  font-size: 13px;
134	  color: rgba(0, 0, 0, 0.35);
135	  transform: translateY(-50%);
136	}
137	
138	.message_sent .message_time {
139	  left: initial;
140	  right: -62px;
141	}
142	
143	.message_name {
144	  margin-top: 5px;
145	  color: rgba(0, 0, 0, 0.55);
146	  font-size: 13px;
147	  font-weight: 600;
148	}
149	
150	.message_sent .message_name {
151	  text-align: right;
152	}
153	
154	.new_message {
155	  padding-top: 15px;
156	  margin-top: 20px;
157	  margin-bottom: 10px;
158	  border-top: 1px solid rgba(0, 0, 0, 0.12);
159	}
160	
161	.new_message_title {
162	  margin-bottom: 15px;
163	}
```
:::

## 5 - 채팅 애플리케이션 테스트

실시간 채팅 앱을 테스트하려면 <font color="#2166ae">`ChatSetup`</font> 컴포넌트를 <font color="#2166ae">`App.js`</font> (또는 <font color="#2166ae">`App.tsx`</font>) JSX 코드에서 선언하고 호출하세요. 다음은 그렇게 할 수 있는 방법의 예입니다:

:::CodeblockTabs
App.js or App.tsx

```javascript
1	import React from "react";
2	import "./App.css";
3	import { initializeParse } from "@parse/react";
4	import { ChatSetup } from "./ChatSetup";
5	
6	// Your Parse initialization configuration goes here
7	// Note the live query URL instead of the regular server url
8	const PARSE_APPLICATION_ID = "YOUR_PARSE_APPLICATION_ID";
9	// const PARSE_SERVER_URL = "https://parseapi.back4app.com/";
10	const PARSE_LIVE_QUERY_URL = "https://YOUR_APP_NAME.b4a.io/";
11	const PARSE_JAVASCRIPT_KEY = "YOUR_PARSE_JAVASCRIPT_KEY";
12	
13	// Initialize parse using @parse/react instead of regular parse JS SDK
14	initializeParse(
15	  PARSE_LIVE_QUERY_URL,
16	  PARSE_APPLICATION_ID,
17	  PARSE_JAVASCRIPT_KEY
18	);
19	
20	function App() {
21	  return (
22	    <div className="App">
23	      <ChatSetup />
24	    </div>
25	  );
26	}
27	
28	export default App;
```
:::

콘솔에서 <font color="#2166ae">`yarn start`</font>를 실행하여 앱을 시작하세요. 이제 다음 화면이 표시되며, 여기서 채팅을 시작하기 위해 보내는 사람과 받는 사람의 닉네임을 입력해야 합니다.

::Image[]{src="https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/hNWQU-JPUktv9kZSmyXBX_image.png" size="80" width="658" height="500" position="center" showCaption="false"}

앱과 실시간 쿼리가 어떻게 작동하는지 더 잘 보기 위해 두 개의 다른 브라우저 창에서 동일한 앱을 열고 나란히 배치하세요. 한 창에서 메시지를 보내면, 닉네임이 일치하고 연결이 활성화되어 있다면 다른 창에서 즉시 나타나는 것을 볼 수 있어야 합니다.

![](https://api.archbee.com/api/optimize/yD3zCY-NNBBIfd0uqcfR5/Mm1yxJC_FolmPtYx-kGkB_image.png)

## 결론

이 가이드를 마치면서, 현실적인 애플리케이션 예제에서 Parse의 실시간 쿼리를 위한 Parse React 훅을 사용하는 방법을 배웠습니다.
