실시간 채팅 메시지를 로컬 상태가 아닌 Supabase 테이블에 저장하기 위해서는 메시지를 전송할 때마다 Supabase 데이터베이스에 삽입(insert) 작업을 수행하고, 채팅 페이지가 로드될 때 메시지를 불러와야 합니다. 이를 위해 다음과 같은 작업을 수행할 수 있습니다:
- 메시지 전송 시 Supabase 테이블에 저장: 사용자가 메시지를 전송할 때, Supabase 테이블에 메시지를 저장합니다.
- 메시지 불러오기: 채팅 컴포넌트가 마운트될 때 Supabase 테이블에서 메시지를 불러옵니다.
먼저, Supabase 클라이언트 설정을 하고 메시지를 저장하고 불러오는 기능을 추가합니다.
"use client";
import supabaseClient from "@/supabase/supabaseClient";
import { Message } from "@/types/typs";
import { RealtimeChannel, RealtimePresenceState, User } from "@supabase/supabase-js";
import { useEffect, useRef, useState } from "react";
function RealTimeTwoPost({ user }: { user: User }) {
const this_user = user;
const [userState, setUserState] = useState<RealtimePresenceState>({});
const [isSubscribed, setIsSubscribed] = useState(false);
const [messages, setMessages] = useState<Message[]>([]);
const channelRef = useRef<RealtimeChannel | null>(null);
const handleMessageSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const message = formData.get("message") as string;
if (channelRef.current && isSubscribed) {
const messageToSend: Message = {
type: "broadcast",
event: "test",
payload: {
message: message,
id: this_user?.id,
},
};
// Save the message to Supabase
await supabaseClient.from('messages').insert([
{ user_id: this_user?.id, message: message }
]);
// Send the message through the channel
channelRef.current.send(messageToSend);
setMessages((prevMessages) => [...prevMessages, messageToSend]);
}
};
useEffect(() => {
const channelA = supabaseClient.channel("room-1");
channelRef.current = channelA;
const messageReceived = (payload: Message) => {
console.log(payload);
setMessages((prevMessages) => [...prevMessages, payload]);
};
channelA.subscribe((status) => {
if (status === "SUBSCRIBED") {
setIsSubscribed(true);
}
});
channelA.on("broadcast", { event: "test" }, (payload) => messageReceived(payload));
// Load messages from Supabase
const loadMessages = async () => {
const { data, error } = await supabaseClient.from('messages').select('*');
if (data) {
const loadedMessages = data.map((msg: any) => ({
type: "broadcast",
event: "test",
payload: {
message: msg.message,
id: msg.user_id,
},
}));
setMessages(loadedMessages);
}
};
loadMessages();
return () => {
channelA.unsubscribe();
};
}, []);
useEffect(() => {
const channel = supabaseClient.channel("online-users", {
config: {
presence: {
key: this_user?.email ? this_user?.email : "Unknown",
},
},
});
channel.on("presence", { event: "sync" }, () => {
const presentState = channel.presenceState();
setUserState({ ...presentState });
});
channel.on("presence", { event: "join" }, ({ newPresences }) => {
console.log("New users have joined: ", newPresences);
});
channel.subscribe(async (status) => {
if (status === "SUBSCRIBED") {
await channel.track({
user_name: this_user?.email ? this_user?.email : "Unknown",
});
}
});
return () => {
channel.unsubscribe();
};
}, []);
return (
<>
<p> 현재 로그인 중인 사람들! </p>
{Object.keys(userState).map((key) => (
<p className="border p-2 rounded-md" key={key}>
Hi ~~! {key}
</p>
))}
<div className="w-full bg-slate-200 rounded-xl">
{messages.map((message, index) => {
const isMyMessage = message.payload.id === this_user?.id;
return (
<p
className={`transition-all duration-300 ease-in-out ${
isMyMessage ? "text-blue-500 text-right" : "text-red-500 text-left"
}`}
key={index}
>
{message.payload.message}
</p>
);
})}
</div>
<form className="w-full flex flex-row mt-4" onSubmit={handleMessageSubmit}>
<input className="bg-gray-200 p-2 rounded-md w-[80%]" type="text" name="message" />
<button className="bg-blue-500 text-white p-2 rounded-md w-[20%]">누르면 메시지 전송</button>
</form>
</>
);
}
export default RealTimeTwoPost;
이 코드에서는 사용자가 메시지를 전송할 때 Supabase 테이블에 메시지를 저장하고, 컴포넌트가 마운트될 때 Supabase에서 기존 메시지를 불러옵니다. Supabase 클라이언트에서 메시지를 가져와 로컬 상태로 설정하여 기존 메시지와 실시간으로 전송된 메시지를 모두 화면에 표시합니다.
옵티미스틱 업데이트(optimistic update)를 사용하면 사용자 경험을 개선하면서도 데이터를 저장할 수 있습니다. 옵티미스틱 업데이트를 사용하면 서버에서 응답을 받기 전에 먼저 사용자 인터페이스를 업데이트한 다음, 서버 응답을 처리합니다. 이렇게 하면 사용자가 메시지를 전송했을 때 빠르게 화면에 메시지를 표시할 수 있습니다.
다음은 옵티미스틱 업데이트를 구현한 예제입니다.
- 메시지 전송 시 UI 즉시 업데이트: 메시지를 전송할 때 먼저 로컬 상태를 업데이트하여 메시지를 화면에 표시합니다.
- 비동기로 데이터베이스에 저장: UI를 업데이트한 후 비동기로 Supabase에 메시지를 저장합니다.
이를 위해 다음과 같이 코드를 수정할 수 있습니다:
"use client";
import supabaseClient from "@/supabase/supabaseClient";
import { Message } from "@/types/typs";
import { RealtimeChannel, RealtimePresenceState, User } from "@supabase/supabase-js";
import { useEffect, useRef, useState } from "react";
function RealTimeTwoPost({ user }: { user: User }) {
const this_user = user;
const [userState, setUserState] = useState<RealtimePresenceState>({});
const [isSubscribed, setIsSubscribed] = useState(false);
const [messages, setMessages] = useState<Message[]>([]);
const channelRef = useRef<RealtimeChannel | null>(null);
const handleMessageSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const message = formData.get("message") as string;
if (channelRef.current && isSubscribed) {
const messageToSend: Message = {
type: "broadcast",
event: "test",
payload: {
message: message,
id: this_user?.id,
},
};
// 옵티미스틱 업데이트 - 먼저 메시지를 화면에 표시
setMessages((prevMessages) => [...prevMessages, messageToSend]);
// 비동기로 Supabase에 메시지 저장
(async () => {
await supabaseClient.from('messages').insert([
{ user_id: this_user?.id, message: message }
]);
// 메시지 저장 후, 필요시 추가 처리를 할 수 있습니다.
})();
// 채널을 통해 메시지 전송
channelRef.current.send(messageToSend);
}
};
useEffect(() => {
const channelA = supabaseClient.channel("room-1");
channelRef.current = channelA;
const messageReceived = (payload: Message) => {
setMessages((prevMessages) => [...prevMessages, payload]);
};
channelA.subscribe((status) => {
if (status === "SUBSCRIBED") {
setIsSubscribed(true);
}
});
channelA.on("broadcast", { event: "test" }, (payload) => messageReceived(payload));
// Supabase에서 메시지 로드
const loadMessages = async () => {
const { data, error } = await supabaseClient.from('messages').select('*');
if (data) {
const loadedMessages = data.map((msg: any) => ({
type: "broadcast",
event: "test",
payload: {
message: msg.message,
id: msg.user_id,
},
}));
setMessages(loadedMessages);
}
};
loadMessages();
return () => {
channelA.unsubscribe();
};
}, []);
useEffect(() => {
const channel = supabaseClient.channel("online-users", {
config: {
presence: {
key: this_user?.email ? this_user?.email : "Unknown",
},
},
});
channel.on("presence", { event: "sync" }, () => {
const presentState = channel.presenceState();
setUserState({ ...presentState });
});
channel.on("presence", { event: "join" }, ({ newPresences }) => {
console.log("New users have joined: ", newPresences);
});
channel.subscribe(async (status) => {
if (status === "SUBSCRIBED") {
await channel.track({
user_name: this_user?.email ? this_user?.email : "Unknown",
});
}
});
return () => {
channel.unsubscribe();
};
}, []);
return (
<>
<p> 현재 로그인 중인 사람들! </p>
{Object.keys(userState).map((key) => (
<p className="border p-2 rounded-md" key={key}>
Hi ~~! {key}
</p>
))}
<div className="w-full bg-slate-200 rounded-xl">
{messages.map((message, index) => {
const isMyMessage = message.payload.id === this_user?.id;
return (
<p
className={`transition-all duration-300 ease-in-out ${
isMyMessage ?
"text-blue-500 text-right" :
"text-red-500 text-left"
}`
}
key={index}
>
{message.payload.message}
</p>
);
})}
</div>
<form className="w-full flex flex-row mt-4" onSubmit={handleMessageSubmit}>
<input className="bg-gray-200 p-2 rounded-md w-[80%]" type="text" name="message" />
<button className="bg-blue-500 text-white p-2 rounded-md w-[20%]">누르면 메시지 전송</button>
</form>
</>
);
}
export default RealTimeTwoPost;
이 코드에서는 메시지를 전송할 때, 먼저 로컬 상태를 업데이트하여 메시지를 화면에 즉시 표시하고, 비동기로 Supabase에 메시지를 저장합니다. 이렇게 하면 사용자는 메시지를 전송할 때 지연 없이 즉시 화면에서 확인할 수 있습니다.
'supabase' 카테고리의 다른 글
[240806 TIL] 쿠키 배달 fetch(supabase auth) (0) | 2024.08.06 |
---|---|
[240730 TIL] supabase upsert (0) | 2024.07.30 |
[240728 TIL] naver 로그인 편법 구현 with Supabase 2편 (0) | 2024.07.28 |
[240727 TIL] naver 로그인 편법 구현 with Supabase 1편 (0) | 2024.07.28 |
[240723 TIL] SQL 팔로잉 수 측정 (0) | 2024.07.24 |