-
10. 메세지 수정사이드프로젝트 2024. 7. 23. 17:01
https://www.youtube.com/watch?v=-xXASlyU0Ck&t=2007s
를 보며 실습하며 정리하는 글
---
이어서 수정기능을 만들어준다.
Shad cn에서 dialog를 검색하고 인스톨해준다.
https://ui.shadcn.com/docs/components/dialog
npx shadcn-ui@latest add dialog
이걸 설치하고, 예시코드를 붙여넣어보면 label이 없다고 빨간줄이 그일텐데, label도 검색해서 install 해준다.
https://ui.shadcn.com/docs/components/label
npx shadcn-ui@latest add label
그리고 Message Action에 Delete 할때와 비슷하게 만들어준다.
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; export function EditAlert() { return ( <Dialog> <DialogTrigger asChild> {/* 삭제할때와 마찬가지로, 빈 버튼. id만 넣어줌 */} <button id="trigger-edit"></button> </DialogTrigger> <DialogContent className="sm:max-w-[425px]"> <DialogHeader> <DialogTitle>Edit profile</DialogTitle> <DialogDescription> Make changes to your profile here. Click save when youre done. </DialogDescription> </DialogHeader> <div className="grid gap-4 py-4"> <div className="grid grid-cols-4 items-center gap-4"> <Label htmlFor="name" className="text-right"> Name </Label> <Input id="name" defaultValue="Pedro Duarte" className="col-span-3" /> </div> <div className="grid grid-cols-4 items-center gap-4"> <Label htmlFor="username" className="text-right"> Username </Label> <Input id="username" defaultValue="@peduarte" className="col-span-3" /> </div> </div> <DialogFooter> <Button type="submit">Save changes</Button> </DialogFooter> </DialogContent> </Dialog> ); }
// components/listMessages.tsx
"use client"; import { useMessage } from "@/lib/store/messages"; import React from "react"; import Message from "./Message"; import { DeleteAlert, EditAlert } from "./MessageActions"; const ListMessages = () => { const messages = useMessage((state) => state.messages); // zustand에 전역으로 저장된 state를 불러옴. return ( <div className="flex-1 flex flex-col p-5 h-full overflow-y-auto"> <div className="flex-1"></div> <div className="space-y-7"> {messages.map((value, idx) => { return <Message key={idx} message={value} />; })} </div> <DeleteAlert /> {/* Edit 추가 */} <EditAlert /> </div> ); }; export default ListMessages;
// components/message.tsx
import { IMessage, useMessage } from "@/lib/store/messages"; import Image from "next/image"; import React from "react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Ellipsis } from "lucide-react"; import { useUser } from "@/lib/store/user"; const MessageMenu = ({ message }: { message: IMessage }) => { // 1. props로 메세지를 받는다. const setActionMessage = useMessage((state) => state.setActionMessage); // 2. setActionMessage를 state에서 받아온다. return ( <DropdownMenu> <DropdownMenuTrigger> <Ellipsis /> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Action</DropdownMenuLabel> <DropdownMenuSeparator /> {/* 요기 추가 */} <DropdownMenuItem onClick={() => { document.getElementById("trigger-edit")?.click(); setActionMessage(message); }} > Edit </DropdownMenuItem> <DropdownMenuItem onClick={() => { document.getElementById("trigger-delete")?.click(); setActionMessage(message); // 3. setActionMessage로 actionMessage에 props로 받아온 메세지를 설정해준다. }} > Delete </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ); }; const Message = ({ message }: { message: IMessage }) => { const user = useUser((state) => state.user); // 유저 정보 가져오기 return ( <div className="flex gap-2"> <div> <Image src={message.users?.avatar_url!} alt={message.users?.display_name!} width={40} height={40} className="rounded-full right-2" /> </div> <div className="flex-1"> <div className="flex items-center justify-between"> <div className="flex items-center gap-1"> <h1 className="font-bold">{message.users?.display_name}</h1> <h1 className="text-sm text-gray-400"> {new Date(message.created_at).toDateString()} </h1> </div> {/* props로 메세지를 넘겨준다. */} {message.users?.id === user?.id && <MessageMenu message={message} />} </div> <p className="text-gray-300">{message.text}</p> </div> </div> ); }; export default Message;
이제 다시 MessageActions.tsx로 가서 EditAlert를 조금 손봐준다. Delete 때랑 거의 비슷하다. state를 가져와서 Input에 Default value로 넣어줌으로써 수정할 메세지가 바로 보이게끔 했다.
export function EditAlert() { const actionMessage = useMessage((state) => state.actionMessage); return ( <Dialog> <DialogTrigger asChild> {/* 삭제할때와 마찬가지로, 빈 버튼. id만 넣어줌 */} <button id="trigger-edit"></button> </DialogTrigger> <DialogContent className="w-full"> <DialogHeader> <DialogTitle>Edit profile</DialogTitle> <DialogDescription> Make changes to your profile here. Click save when youre done. </DialogDescription> </DialogHeader> <Input id="username" defaultValue={actionMessage?.text} /> <DialogFooter> <Button type="submit">Save changes</Button> </DialogFooter> </DialogContent> </Dialog> ); }
이제 에딧 버튼을 클릭해보면 고칠 메세지가 잘 나온다.
이제 supabase를 연결해서 디비에있는 데이터를 수정할 차례다.
delete 할때와 거의 비슷하게 Edit Alert를 수정해준다. supabase 연결하고, 수정 리퀘스트를 보낸다.
export function EditAlert() { const actionMessage = useMessage((state) => state.actionMessage); const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>; const handleEdit = async () => { const supabase = createClient(); const text = inputRef.current.value; if (text) { const { error } = await supabase .from("messages") .update({ text, is_edit: true }) .eq("id", actionMessage?.id!); if (error) { toast.error(error.message); } else { toast.success("Successfully update a message"); } document.getElementById("trigger-edit")?.click(); } }; return ( <Dialog> <DialogTrigger asChild> {/* 삭제할때와 마찬가지로, 빈 버튼. id만 넣어줌 */} <button id="trigger-edit"></button> </DialogTrigger> <DialogContent className="w-full"> <DialogHeader> <DialogTitle>Edit Message</DialogTitle> <DialogDescription>Edit Message</DialogDescription> </DialogHeader> <Input defaultValue={actionMessage?.text} ref={inputRef} /> <DialogFooter> <Button type="submit" onClick={handleEdit}> Save changes </Button> </DialogFooter> </DialogContent> </Dialog> ); }
이제 메세지를 수정하고 새로고침해보면 수정되는 걸 볼 수 있다.
그리고 이번에도 역시 Optimistic Update를 해준다.
lib/store/messages.ts
import { create } from "zustand"; export type IMessage = { created_at: string; id: string; is_deleted: string | null; is_edit: boolean; text: string; user_id: string; users: { avatar_url: string; created_at: string; deleted_at: string | null; display_name: string; id: string; updated_at: string | null; } | null; }; interface MessageState { messages: IMessage[]; addMessage: (message: IMessage) => void; actionMessage: IMessage | undefined; setActionMessage: (message: IMessage | undefined) => void; optimisticDeleteMessage: (messageId: string) => void; optimisticUpdateMessage: (message: IMessage) => void; } export const useMessage = create<MessageState>()((set) => ({ messages: [], addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })), // 기존 state에 담겨있던 message들 + 이번에 작성한 메세지 actionMessage: undefined, setActionMessage: (message) => set(() => ({ actionMessage: message })), optimisticDeleteMessage: (messageId) => set((state) => { return { messages: state.messages.filter((message) => message.id != messageId), }; }), optimisticUpdateMessage: (updateMessage) => set((state) => { return { messages: state.messages.filter((message) => { if (message.id == updateMessage.id) { message.text = updateMessage.text; message.is_edit = updateMessage.is_edit; } return message; }), }; }), }));
다시 components/MessageActions.tsx 안의 EditAlert
export function EditAlert() { const actionMessage = useMessage((state) => state.actionMessage); const optimisticUpdateMessage = useMessage( (state) => state.optimisticUpdateMessage ); const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>; const handleEdit = async () => { const supabase = createClient(); const text = inputRef.current.value.trim(); if (text) { optimisticUpdateMessage({ ...actionMessage, text, is_edit: true, } as IMessage); const { error } = await supabase .from("messages") .update({ text, is_edit: true }) .eq("id", actionMessage?.id!); if (error) { toast.error(error.message); } else { toast.success("Successfully update a message"); } document.getElementById("trigger-edit")?.click(); } else { // 메세지가 없을 경우 처리. Delete Alert을 띄운다. document.getElementById("trigger-edit")?.click(); document.getElementById("trigger-delete")?.click(); } }; return ( <Dialog> <DialogTrigger asChild> {/* 삭제할때와 마찬가지로, 빈 버튼. id만 넣어줌 */} <button id="trigger-edit"></button> </DialogTrigger> <DialogContent className="w-full"> <DialogHeader> <DialogTitle>Edit Message</DialogTitle> <DialogDescription>Edit Message</DialogDescription> </DialogHeader> <Input defaultValue={actionMessage?.text} ref={inputRef} /> <DialogFooter> <Button type="submit" onClick={handleEdit}> Save changes </Button> </DialogFooter> </DialogContent> </Dialog> ); }
이제 수정을 누르면 바로 optimistic update가 되는 걸 볼 수 있다.
또, 여기서 메세지를 다 지우고 Edit을 할경우 Delete Alert을 띄우도록 했다.
이제 components/message.tsx로 가서 메세지가 업데이트 되었다는걸 표시해주자.
import { IMessage, useMessage } from "@/lib/store/messages"; import Image from "next/image"; import React from "react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Ellipsis } from "lucide-react"; import { useUser } from "@/lib/store/user"; const MessageMenu = ({ message }: { message: IMessage }) => { // 1. props로 메세지를 받는다. const setActionMessage = useMessage((state) => state.setActionMessage); // 2. setActionMessage를 state에서 받아온다. return ( <DropdownMenu> <DropdownMenuTrigger> <Ellipsis /> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuLabel>Action</DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem onClick={() => { document.getElementById("trigger-edit")?.click(); setActionMessage(message); }} > Edit </DropdownMenuItem> <DropdownMenuItem onClick={() => { document.getElementById("trigger-delete")?.click(); setActionMessage(message); // 3. setActionMessage로 actionMessage에 props로 받아온 메세지를 설정해준다. }} > Delete </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ); }; const Message = ({ message }: { message: IMessage }) => { const user = useUser((state) => state.user); // 유저 정보 가져오기 return ( <div className="flex gap-2"> <div> <Image src={message.users?.avatar_url!} alt={message.users?.display_name!} width={40} height={40} className="rounded-full right-2" /> </div> <div className="flex-1"> <div className="flex items-center justify-between"> <div className="flex items-center gap-1"> <h1 className="font-bold">{message.users?.display_name}</h1> <h1 className="text-sm text-gray-400"> {new Date(message.created_at).toDateString()} </h1> { // 요기!! 수정되었다고 표시 message.is_edit && ( <h1 className="text-sm text-gray-400">edited</h1> ) } </div> {/* props로 메세지를 넘겨준다. */} {message.users?.id === user?.id && <MessageMenu message={message} />} </div> <p className="text-gray-300">{message.text}</p> </div> </div> ); }; export default Message;
밑에 html 부분에 요기!! 수정되었다고 표시 쪽에 보면 메세지의 is_edit이 true일 때, edited가 표시되도록 한 걸 볼 수 있다.
Github : https://github.com/Wunhyeon/Next-Supabase-Chat/tree/10.UpdateMessage
'사이드프로젝트' 카테고리의 다른 글
12. Arrow down & notification (4) 2024.07.24 11.실시간 통신 (3) 2024.07.23 9. Message Delete (7) 2024.07.23 8.Message Menu (1) 2024.07.22 6. 메세지 리스트 표시하기 (4) 2024.07.22