개발자

채팅 기능 client 가 null 값이 돼요

2023년 08월 14일조회 222

하나의 페이지에서 connectHandler를 작동하고 또다른 페이지에서 sendHandler를 작동하려고 하는데 이렇게 해서는 useChat()이 리렌더링 되면서 client 값이 초기화가 되더라구요 값을 유지하고 싶고 recoil에 client를 담는 건 불가능이라고 떠서... connectHandler와 sendHandler를 다른 hooks로 분리하는 방법도 생각해봤는데 그러면 또 client값이 connect한 값이 아니더라구요 무슨 방법이 있을까요? 제발 도와주세요 ㅠㅠ (한 페이지에서 connectHandler, sendHandler, disconnectHandler 실행하면 잘 작동합니다!) import { CompatClient, Stomp } from "@stomp/stompjs"; import { useRef } from "react"; import { useRecoilState } from "recoil"; import { inputMessageState, messageState } from "../../states/chatting"; export function useChat() { const [messages, setMessages] = useRecoilState(messageState); const [inputMessage, setInputMessage] = useRecoilState(inputMessageState); const token = localStorage.getItem("accessToken"); // 채팅 연결 구독 const client = useRef<CompatClient>(); const connectHandler = () => { client.current = Stomp.over(() => { const sock = new WebSocket("wss://m-ssaem.com:8080/stomp/chat"); return sock; }); client.current.connect( { token: token, }, () => { client.current && client.current.subscribe(`/sub/chat/room/1`, onMessageReceived, { token: token!, }); }, ); }; const onMessageReceived = (message: any) => { setMessages((prevMessage) => [...prevMessage, JSON.parse(message.body)]); }; // 채팅 나가기 const disconnectHandler = () => { if (client.current) { client.current.disconnect(() => { window.location.reload(); }); } }; // 채팅 보내기 const sendHandler = () => { if (client.current && inputMessage.trim() !== "") { client.current.send( `/pub/chat/message`, { token: token, }, JSON.stringify({ roomId: 1, message: inputMessage, type: "TALK", }), ); setInputMessage(""); } }; return { connectHandler, disconnectHandler, sendHandler, }; }

이 질문이 도움이 되었나요?
'추천해요' 버튼을 누르면 좋은 질문이 더 많은 사람에게 노출될 수 있어요. '보충이 필요해요' 버튼을 누르면 질문자에게 질문 내용 보충을 요청하는 알림이 가요.
profile picture
익명님의 질문

답변 1

L님의 프로필 사진

지금 구조에선 clientRef는 useChat이 사용되는 곳 마다 초기화 될 수 밖에 없습니다. 방법 중 하나론 Context API로 전역에서 사용하거나 원하는 범위에서 사용하는 방법이 있을 것 같습니다. import React, { createContext, useContext, useState, useEffect } from 'react'; const ChatContext = createContext(); export const ChatProvider = ({ children }) => { const handlers = useMemo(() => ({ connect, disconnect, send }), []) return ( <ChatContext.Provider value={handlers}> {children} </ChatContext.Provider> ); }; export const useChat = () => { return useContext(ChatContext); }; // App.tsx 혹은 어디든.. import { ChatProvider } from './ChatContext'; function App() { return ( /* 리코일 루트보다 하위 */ <ChatProvider> {/* 나머지 컴포넌트들 */} </ChatProvider> ); } export default App; 토큰은 connect 한테 인자로 받는게 좋을거 같습니다. 보다보니 계속 수정하게 되네요. 대신 이렇게 쓰면 리코일 상태는 같이 안쓰는 쪽이 좋을거 같습니다. sendHandler도 인풋메시지를 인자로 받는 편이 좋아보입니다.

profile picture

익명

작성자

2023년 08월 14일

와 진짜 너무 감사해요 ㅠㅠㅠ 2,3일 고민하고 있던 문제를 해결했습니다 너무 감사합니다! 참고해서 더 효율적인 코드 작성하겠습니다!!

L님의 프로필 사진

L

Frontend Developer2023년 08월 14일

조금 덧붙이자면 기존 코드에선 useChat이 리렌더가 되는게 아니라 사용하는 곳마다 새로운 인스턴스를 사용하는 상황이에요! 하나의 인스턴스를 공유할 수 있는 다른 방법들 중 플젝에 더 맞는 방법이 있을 수 있으니 고민해보시길 바랍니다!

profile picture

익명

작성자

2023년 08월 14일

모든 페이지에서 새로고침을 하면 connect, subscribe 한 것들이 없어지는데 유지할 수 있는 방법 없을까요?

profile picture

익명

작성자

2023년 08월 14일

import { CompatClient, Stomp } from "@stomp/stompjs"; import { createContext, useContext, useMemo, useRef } from "react"; import { useSetRecoilState } from "recoil"; import { messageState } from "../../states/chatting"; const ChatContext = createContext( {} as { connect: (roomId: number) => void; disconnect: () => void; send: (roomId: number, message: string) => void; }, ); export const useChatContext = () => useContext(ChatContext); export function ChatProvider({ children }: any) { const setMessages = useSetRecoilState(messageState); const token = localStorage.getItem("accessToken"); // 채팅 연결 구독 const client = useRef<CompatClient>(); const connect = (roomId: number) => { client.current = Stomp.over(() => { const sock = new WebSocket("wss://m-ssaem.com:8080/stomp/chat"); return sock; }); client.current.connect( { token: token, }, () => { client.current && client.current.subscribe( `/sub/chat/room/${roomId}`, onMessageReceived, { token: token!, }, ); }, ); return client; }; const onMessageReceived = (message: any) => { setMessages((prevMessage) => [...prevMessage, JSON.parse(message.body)]); }; // 채팅 나가기 const disconnect = () => { if (client.current) { client.current.disconnect(() => { window.location.reload(); }); } }; // 채팅 보내기 const send = (roomId: number, message: string) => { if (client.current) { client.current.send( `/pub/chat/message`, { token: token, }, JSON.stringify({ roomId: roomId, message: message, type: "TALK", }), ); } }; const handlers = useMemo(() => ({ connect, disconnect, send }), []); return ( <ChatContext.Provider value={handlers}>{children}</ChatContext.Provider> ); }

L님의 프로필 사진

L

Frontend Developer2023년 08월 14일

글쎄요.. 작성자 분이 정확히 어떤 상황인지 모르겠는데 현상에 대한 답변만 드리면 이펙트에서 커넥트를 넣어볼 수도 있고요. 스톰프제이에스에서 제공한다면 리커넥트 시도하는 로직을 넣어볼 수도 있구요. 지금 로직은 커넥트를 버튼이나 무언가로 호출하실 것만 같은 상황인거처럼 보입니다.

profile picture

익명

작성자

2023년 08월 15일

네 맞습니다! 현재 connect를 [채팅 시작] 버튼에서 호출하고 있으며 버튼을 누르면 채팅 페이지로 이동합니다. 새로고침 할 경우에 connect는 다시 실행되지 않는데, 연결이 끊기는 이유가 뭘까요 ....

L님의 프로필 사진

L

Frontend Developer2023년 08월 15일

말씀드린대로 페이지를 새로고침할 때, 현재 페이지의 모든 상태와 메모리에 있는 데이터는 초기화되며, JavaScript의 실행 환경도 다시 시작됩니다. 따라서 WebSocket과 같은 연결도 닫히게 되고, 다시 연결해야 합니다. 기본적으로 웹 브라우저는 새로고침을 할 때 현재 페이지의 모든 리소스와 상태를 초기화하고, 페이지를 처음부터 다시 로드하기 때문입니다. 유즈이펙트에 커넥트를 시도하거나 스톰제이에스가 뭔지모르겠지만 리커넥트 하는 로직을 추가하셔야 합니다

profile picture

익명

작성자

2023년 08월 15일

친절한 답변 감사합니다!!! 참고해서 수정해보도록 하겠습니다 :)

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

목록으로
키워드로 질문 모아보기

실무, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!