Как правильно инициализировать соединение по websocket в компоненте react?

Предыстория: я backend разработчик, для своего проекта написал фронт на реакт. Все работает, но меня немного смущает одна вещь: один из компонентов использует подключение по websocket, и в консоли разработчика вижу что подключение по ws инициализируется сразу при монтировании радительского компонента, что логично т.к. в дочернем компоненте объект ws объявлен до функции по умолчанию.

Чего хочу: чтобы инстанс ws создавался при монтировании дочернего компонента, а при размонтировании корректно завершал соединение вызовом .close() и так же при монтировании компонента нужно по этому же вебсокету ровно один раз при каждом монтировании (сейчас у меня просто ws.send() в useEffect с пустыми зависимостями) посылать одно сообщение.

Код на котором проводил эксперименты:

import { useState, useRef, useEffect } from 'react'

// Родительский компонент
function App() {
  const [showChild, setShowChild] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChild(!showChild)}>
        {showChild ? 'Убрать дочерний компонент' : 'Показать дочерний компонент'}
      </button>
      {showChild && <ChildComponent />}
    </div>
  );
}

// Дочерний компонент
function ChildComponent() {
  const [message, setMessage] = useState('');
  const [inputValue, setInputValue] = useState('');
  const socketRef = useRef(null);

  useEffect(() => {
    // Инициализация WebSocket при монтировании компонента
    socketRef.current = new WebSocket('ws://your-backend-url');

    // Отправка сообщения "привет" при открытии соединения
    socketRef.current.onopen = () => {
      socketRef.current.send('привет');
    };

    // Обработка входящих сообщений
    socketRef.current.onmessage = (event) => {
      setMessage(event.data);
    };

    // Обработка ошибок
    socketRef.current.onerror = (error) => {
      console.error('WebSocket error:', error);
      setMessage('Ошибка соединения');
    };

    // Закрытие соединения при размонтировании компонента
    return () => {
      if (socketRef.current) {
        socketRef.current.close();
      }
    };
  }, []);

  const handleSendMessage = () => {
    if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
      socketRef.current.send(inputValue);
      setInputValue('');
    }
  };

  return (
    <div style={{ marginTop: '20px', border: '1px solid #ccc', padding: '10px' }}>
      <div>Полученное сообщение: <strong>{message}</strong></div>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="Введите сообщение"
      />
      <button onClick={handleSendMessage}>Отправить</button>
    </div>
  );
}


export default App

но вебсокет закрывается до установления соединения, а если убрать close из useEffect не понимаю как завершить корректно.

Просьба подсказать что не так делаю.


Ответы (1 шт):

Автор решения: vitalii petrenko

по итогу дело было в strict mode, который перерисовывает компоненты несколько раз. минимально рабочий код в build:

import { useState, useRef, useEffect } from 'react'


// Родительский компонент
function App() {
  const [showChild, setShowChild] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChild(!showChild)}>
        {showChild ? 'Убрать дочерний компонент' : 'Показать дочерний компонент'}
      </button>
      {showChild && <ChildComponent />}
    </div>
  );
}

// Дочерний компонент
function ChildComponent() {
  const [message, setMessage] = useState('');
  const [inputValue, setInputValue] = useState('');
  const socketRef = useRef(null);

  useEffect(() => {
    // Инициализация WebSocket при монтировании компонента
    socketRef.current = new WebSocket('wss://echo.websocket.org/');

    // Отправка сообщения "привет" при открытии соединения
    socketRef.current.onopen = () => {
      socketRef.current.send('привет');
    };

    // Обработка входящих сообщений
    socketRef.current.onmessage = (event) => {
      setMessage(event.data);
    };

    // Обработка ошибок
    socketRef.current.onerror = (error) => {
      console.error('WebSocket error:', error);
      setMessage('Ошибка соединения');
    };

    // Закрытие соединения при размонтировании компонента
    return () => {
      if (socketRef.current) {
        socketRef.current.close(1000);
      }
    };
  }, []);

  const handleSendMessage = () => {
    if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
      socketRef.current.send(inputValue);
      setInputValue('');
    }
  };

  return (
    <div style={{ marginTop: '20px', border: '1px solid #ccc', padding: '10px' }}>
      <div>Полученное сообщение: <strong>{message}</strong></div>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="Введите сообщение"
      />
      <button onClick={handleSendMessage}>Отправить</button>
    </div>
  );
}



export default App

Этот код делает именно то, что мне нужно в части работы с websocket

→ Ссылка