Проблема с динамическим обновлением компонента React в проекте чата

#reactjs

Вопрос:

В моем приложении для чата у меня возникают проблемы с отображением сообщений пользователей после нажатия кнопки «Отправить».

Я получаю все данные сообщений из API и сохраняю их в состоянии. Вот соответствующая часть.

Я упростил код, потому что думаю, что проблема в логике моего кода и в нем нет синтаксических ошибок.

 const Chat = (props) => {
  const [selectedRoom, setSelectedRoom] = useState({})
  const [roomMessages, setRoomMessages] = useState([])
  let roomMessagesData = []
  
  function getUserRoomMessages() {
     fetch("url").then((response) => {
        return response.json();
     }).then((requestData) => {
        setRoomMessages(requestData.message_list)
     });
  };
  
  function sendMessage(message) {
     fetch("url").then((response) => {
        return response.json();
     }).then((requestData) => {
        getUserRoomMessages()
     });
  } 

  function handleSendMessageButton() {
     let currentMsg = document.querySelector("#textBox").value;
     if (currentMsg != "") {
        sendMessage(currentMsg)
        roomMessagesData.push(
          <div className="message receiver">{currentMsg}</div>
        )
     }
     document.querySelector("#textBox").value = "";
  }

  useEffect(() => {
     getUserRoomMessages()
  }, [selectedRoom])

  // Note that: 
  // typeof roomMessages !== 'undefined' will always be true,
  // since initially, roomMessages = [], it is defined.
  // Instead just check if roomMessages is not empty, to avoid this operation.
  
  if (roomMessages.length > 0) {
  
     const sortedMessages = roomMessages.sort(
        (a,b)=> a.message_id > b.message_id ? 1 : -1
     );
     for (let i = 0; i<sortedMessages.length; i  ) {
        if (sortedMessages[i].creator_id == user_id) {
           roomMessagesData.push(
              <div className="message receiver">
                {sortedMessages[i].message_text}
              </div>
           );
        } else {
           roomMessagesData.push(
              <div className="message sender"> 
                {sortedMessages[i].message_text}
              </div>
           );
        }
     };
  }
  
  return (
     <div>
       //some code
       <div className="chatBox">{roomMessagesData}</div>
       <div><input type="text" id="textBox"/></div>
       <div>
         <input
            id="btn"
            type="button"
            value="Send message" 
            onClick={handleSendMessageButton}
         />
       </div>
     </div>
  )
}
 

В этой текущей логике, когда я нажимаю кнопку отправить сообщение, сообщение успешно отправляется в API, но не отображается. Только после того, как я обновлю страницу, появится новое сообщение.

Все вызовы API завершены успешно. Моя идея заключается в том, что, когда пользователь отправляет новое сообщение, я получаю недавно обновленный список сообщений из API, которые затем должны быть повторно отрисованы reactjs . Или я хочу подтолкнуть новый элемент roomMessagesData так, чтобы после его изменения reactjs следовало перезагрузить его элементы.

Вопрос в том, в чем проблема в моем коде и что я должен сделать, чтобы достичь того, что я описал выше.

Комментарии:

1. typeof roomMessages !== 'undefined' вам не нужно этого делать. достаточно простого if (roomMessages) . в JS if возвращает a true , в операторе if, если значение параметра НЕ null равно, undefined , 0 (для чисел), false (obv).

2. Я не уверен, что полностью понял ваш код, и если именно поэтому сообщения не отображаются, но только переменные useState являются компонентом повторной передачи, обычных-нет.

Ответ №1:

Прямо сейчас вы определили свой список с помощью переменной let roomMessagesData = [] ,в то время как для его определения вам нужно использовать состояние. позже , когда вы измените это состояние, ваш компонент будет обновлен с момента изменения состояния. другой способ-развернуть крючок useEffect, в котором вы будете проходить roomMessagesData как вторая зависимость. Таким образом , когда этот массив изменится, ваш компонент будет повторно отправлен.

 useEffect(() => {
       const copyRoomMessagesData = [...roomMessagesData]
  }, [roomMessagesData])
 
 <div className="chatBox"> 
         {copyRoomMessagesData}
       </div>
 

Комментарии:

1. Поскольку он уже определился roomMessages как СОСТОЯНИЕ и roomMessagesData зависит от этого состояния… поэтому вместо перехода roomMessagesData в состояние… он должен просто добавить roomMessages в качестве «зависимой» переменной внутри useEffect . Ты так не думаешь?

2. Это тоже не сработает, так как вы будете попадать в крючок useEffect с каждым изменением, но это не меняет того факта, что вы roomMessagesData не будете перенаправлены.

3. Помните, что если roomMessages что-то меняется, значит roomMessagesData , меняется… ты согласен? И если да, то в случае roomMessages изменений useEffect будет выполняться повторная визуализация компонента.. согласны? И roomMessages изменения происходят только после того, как a handleSendMessageButton запускается изнутри Input … Он использует onClick не onChange … кстати!! На самом деле, он должен перенести это onClick с Input button » в » на «а».

4. @MwamiTovi Ну, он должен многое изменить в своем коде, но давайте придерживаться реальной цели здесь. во-первых, я изучил то, что предложил U, но разве это не приводит к бесконечному циклу? В том, что вы предложили, мы в основном устанавливаем состояние внутри крючка useEffect, который является observingit ( не устанавливая его прямо внутри него, а вызывая функцию, которая это делает ). во-вторых, я не согласен с тем, что вы сказали о повторной визуализации компонента. Возможно, я немного ошибаюсь, но я думаю, что будет повторно отображена только та часть нашего компонента, которую мы вызываем внутри крючка useEffect.

5. На самом деле я понимаю вашу точку зрения, потому getUserRoomMessages что обновляет roomMessages состояние, и оно называется внутри useEffect … да, я предвижу бесконечный цикл. Это мост, который мы можем пересечь, если ему нужно, чтобы мы пошли туда… он должен удалить этот getUserRoomMessages вызов, потому что, помимо создания бесконечного цикла, он также запускает бесконечные вызовы API! Но цель, которую он описывает, для повторного отображения компонента возможна только путем добавления правильного «зависимого» состояния/переменных в useEffect .

Ответ №2:

Краткий обзор шагов:

  • Таким handleSendMessageButton образом, запускается sendMessage функция, которая выполняет a POST .
  • А затем sendMessage запускает GET сообщение с помощью getUserRoomMessages .
  • Внутри getUserRoomMessages вы запускаете обновление состояния с помощью setRoomMessages .
  • И отображаемый ребенок {roomMessagesData} зависит от изменений в roomMessages состоянии.

Исходя из вышесказанного, проблема, которую необходимо устранить, находится здесь:

 // ONLY re-renders if selectedRoom changes
useEffect(() => { getUserRoomMessages() }, [selectedRoom]);
 

Измените вышеизложенное на:

 // re-render if roomMessages changes,
// and the message should display automatically
useEffect(() => { getUserRoomMessages() }, [roomMessages]);
 

Помните, что useEffect() это повторится только в том случае, если изменятся определенные «зависимые» состояния.

Обновить

Несмотря на предложение выше, обратите внимание, что он создаст infinite loop , потому что — getUserRoomMessages вызовет вызов API, а затем обновит roomMessages состояние, от которого useEffect зависит повторная визуализация компонента, что приведет к бесконечной повторной визуализации и многочисленным вызовам API.

Предложение:

Так getUserRoomMessages как уже вызывается при onClick срабатывании, таким образом, дата обновления… вместо этого вы можете использовать эффект использования для запуска только при обновлении состояния, например

 let loaded = React.useRef(false); 

// Cleanup useEffect
useEffect(() => {
  // set to true on mount...
  isLoaded.current = true;
  // ... and to false on unmount
  return () => { isLoaded.current = false; };
}, [isLoaded, roomMessages]);