I want to get initial state data using https api, react-query
and update it with web socket, stompjs
in this component, 'useGetCheckQuestions' custom hook get the initial data from https api and store it in 'items' state if isLoading finished.
Then it connects to Socket and if it's onConnect (connect success part), subscribe all the item in items array
The problem is, in socket.onConnect items get empty array. I want items get the initial setting which is using custom hook at that moment:
const QuestionCheckModal = ({
isOpen,
setIsOpen,
setIsOpenModal,
}: BaseModalProps) => {
let { user_id } = useParams();
const userIdNumber: number = parseInt(user_id, 10);
const [isHovered, setIsHovered] = useState(false);
const [items, setItems] = useState([]);
const [isSocketOpen, setIsSocketOpen] = useState(false);
const [stompClient, setStompClient] = useState<Stomp.Client | null>(null);
//Initial data setting
const checkQuestionData = useGetCheckQuestions(userIdNumber);
useEffect(() => {
if (!checkQuestionData.isLoading) {
console.log('확인 질문 데이터 세팅', checkQuestionData);
setItems(checkQuestionData.checkQuestion);
}
}, [!checkQuestionData.isLoading]);
const moveItem = (dragIndex: number, hoverIndex: number) => {
const draggedItem = items[dragIndex];
setItems((prevItems) => {
const newItems = [...prevItems];
newItems.splice(dragIndex, 1);
newItems.splice(hoverIndex, 0, draggedItem);
return newItems;
});
};
const handleBtn = () => {
setIsOpenModal(true);
setIsOpen(!isOpen);
};
/**
* websocket part
*/
const token = localStorage.getItem('accessToken');
//make client
const socket = new Client({
brokerURL: `wss://gotchaa.shop/ws`,
debug: function (str) {
console.log(str);
},
connectHeaders: {
Authorization: `Bearer ${token}`,
},
reconnectDelay: 5000, // 자동 재 연결
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
//send msg
const handlePubQuestion = ({ questionId, questionBody }: QuestionProps) => {
const strQuestionBody = JSON.stringify(questionBody);
if (stompClient && stompClient.connected) {
stompClient.publish({
destination: `/pub/question/${questionId}`,
body: strQuestionBody,
headers: { Authorization: `Bearer ${token}` },
});
}
};
//get msg
const handleConnectSubQuestion = (questionId: number) => {
socket.subscribe(
`/sub/question/${questionId}`,
(message: any) => handleGetSubQuestion(message, questionId),
{
Authorization: `Bearer ${token}`,
}
);
};
//메세지 받으면 실행되는 콜백함수
const handleGetSubQuestion = (message: any, questionId: number) => {
if (message.body) {
const parsedBody = JSON.parse(message.body);
// Find the item with the matching questionId
const updatedItems = items.map((currentItem) => {
if (currentItem.id === questionId) {
// Check the type of the message and update accordingly
switch (parsedBody.type) {
case 'IMPORTANCE':
currentItem.importance = parsedBody.value;
break;
case 'CONTENT':
currentItem.content = parsedBody.value;
break;
case 'DELETE':
// Remove the item from the array
return null;
default:
// Handle other types if needed
break;
}
}
return currentItem;
});
// Remove null values (deleted items) from the array
const filteredItems = updatedItems.filter((item) => item !== null);
// Update the state with the modified array
setItems(filteredItems);
alert(
`Updated item with questionId ${questionId}. New state: ${JSON.stringify(
filteredItems
)}`
);
} else {
alert('got empty message');
}
};
//connect success
socket.onConnect = (frame) => {
console.log('소켓 연결 성공');
setStompClient(socket);
console.log(items);
//item당 열기
items.forEach(function (item) {
//구독 소켓 연결
console.log('열려라' + item.id);
handleConnectSubQuestion(item.id);
});
};
socket.onStompError = function (frame) {
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
useEffect(() => {
console.log('소켓 연결 시작');
socket.activate();
return () => {
//unmount
console.log('소켓 연결 끝');
socket.deactivate();
};
}, []);
return (
<>
{isOpen && (
<Container>
<Topbar>
<InfoBox>
<Title>갓차린 면접자 질문 확인</Title>
<Info
src={info}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
/>
{isHovered && (
<InfoPopup>
면접 질문을 드래그해서 순서를 변경할 수 있어요.
<br />
여기서 정한 질문 순서대로 면접 중 질문이 나타나요.
</InfoPopup>
)}
</InfoBox>
<StartBtn onClick={handleBtn}>면접 전형 시작</StartBtn>
</Topbar>
<Box>
<QuestionContainer>
<DndProvider backend={HTML5Backend}>
{items.map((item, index) => (
<QuestionItemDrag
key={item?.id}
isCommon={item?.common}
content={item?.content}
importance={item.importance}
index={item?.id}
moveItem={moveItem}
//wss
handleSub={handleConnectSubQuestion}
handlePub={handlePubQuestion}
isSocketOpen={isSocketOpen}
socket={socket}
/>
))}
</DndProvider>
</QuestionContainer>
</Box>
</Container>
)}
</>
);
};
i tried locate socket activate func after checkQuestionData isLoading is false and items data is set but it works same as before
and tried using useEffect with items dependency so it can activate if items.length is non zero but then it gets problem of infinite activation
then lastly, i changed items to following
checkQuestionData.checkQuestion
then it works but items state get wrong after each render