рендеринг сообщений в чате

Рейтинг: 0Ответов: 1Опубликовано: 19.05.2023

Есть 2 метода отправки и получения сообщений в чате, при положительном ответе от сервера я добавляю сообщения в чат.

const [messages, setMessages] = useState([])

function sendMessage () {
if (value !== '') {
  api
    .sendMessage(value)
    .then(() => {
      setMessages([{ message: value, ownMessage: true }, ...messages])
      setValue('')
    })
    .catch(err => console.log(`Произошла ошибка: ${err}`))
}

}

function getMessage () {
api
  .recieveNotification()
  .then(res => {
    console.log(res)
    if (res !== null) {
      if (chatIdDisplayed === res.body.senderData.chatId) {
        setMessages([
          {
            message: res.body.messageData.textMessageData.textMessage,
            ownMessage: false
          },
          ...messages
        ])
        console.log(messages)
        api.deleteNotification(res.receiptId)
      } else {
        api.deleteNotification(res.receiptId)
      }
    } else {
      console.log('Уведомлений нет')
    }
  })
  .catch(err => {
    console.log(err)
  })

}

И сам рендеринг:

{messages.map((entry, id) => (
          <li
            key={id}
            className={`main__chat-message ${
              !entry.ownMessage ? 'main__chat-message_chat-partner' : ''
            }`}
          >
            {[entry.message, entry.ownMessage]}
          </li>
        ))}

Когда вводит сообщение пользователь, то все отрисовывается нормально, но когда сообщения приходят в ответ, поведение становится странным, исходящие сообщения полностью удаляются, появляется входящее, которые с каждым последующим входящим заменяется на новое...Подскажите кто знает, как исправить что бы нормально отрисовывались

Вызов getMessage:

useEffect(() => {
if (isData) {
  const setIntervalFunction = () => {
    setInterval(getMessage, 7000)
  }
  setIntervalFunction()
}

}, [isData])

const {
  useState,
  useEffect
} = React;

function App() {
  // стейт для отображения сообщений
  const [messages, setMessages] = useState([])

  // стейт инпута
  const [value, setValue] = useState('')

  const makeid = () => {
    let text = ''
    const possible = 'QWER0123456789'

    for (let i = 0; i < 15; i++)
      text += possible.charAt(Math.floor(Math.random() * possible.length))

    return text
  }

  let varId = makeid()
  let varId2 = makeid()

  function sendMessage() {
    if (value !== '') {
      setMessages([{
          message: value,
          ownMessage: true,
          id: varId
        },
        ...messages
      ])
      setValue('')
    }
    console.log(messages)
  }

  function getMessage() {
    console.log('getMessage')
    setMessages([{
        message: 'Входящее сообщение',
        ownMessage: false,
        id: varId2
      },
      ...messages
    ])
  }

  useEffect(() => {
    const setIntervalFunction = () => {
      setInterval(getMessage, 3000)
    }
    setIntervalFunction()
  }, [])

  return ( < div >
    <
    ul className = 'main__chat-container' > {
      messages.map(entry => ( <
        li key = {
          entry.id
        }
        className = {
          `main__chat-message`
        } > {
          [entry.message, entry.ownMessage]
        } <
        /li>
      ))
    } <
    /ul> < /
    div > )
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render( < App / > )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Ответы

▲ 2Принят

Проблема в неверном key. В данном случае для ключа выбран index в массиве.

При добавлении вручную - добавляется в конец массива и у каждого нового элемента новый индекс.

При добавлении по событию - добавляется в начало массива, следовательно индексы элементов в старом и новом массивах перестают совпадать.

Из-за этого при рендере могут быть неожиданные результаты.

Для решения нужно выбирать key так, чтобы он был уникальным. Например завести поле id и заполнять его при добавлении элемента.


Кроме указанного выше недочета, есть несколько ошибок с замыканием значений.

  1. генерация id которые используются для сообщений - значения замыкаются и используются одни и те же. Для решения достаточно перенести получение id в место, где создается сообщение
  2. метод getMessage передаваемый в setInterval - ссылка на функцию замыкается, внутри функции замыкается значение messages (пустой массив) - поэтому. Решений может быть несколько: добавить getMessages в список зависимостей, использовать сеттер с коллбэком

Лучше всего не использовать внутри useEffect функции меняющиеся между рендерами, в противном случае необходимо добавлять их в список зависимостей, чтобы useEffect обновился при изменении.

const {
  useState,
  useEffect
} = React;

function App() {
  // стейт для отображения сообщений
  const [messages, setMessages] = useState([])

  // стейт инпута
  const [value, setValue] = useState('')

  const makeid = () => {
    let text = ''
    const possible = 'QWER0123456789'

    for (let i = 0; i < 15; i++)
      text += possible.charAt(Math.floor(Math.random() * possible.length))

    return text
  }

  function sendMessage() {
    if (value !== '') {
      let varId = makeid();
      setMessages([{
          message: value,
          ownMessage: true,
          id: varId
        },
        ...messages
      ])
      setValue('')
    }
    console.log(messages)
  }

  function getMessage() {
    let varId2 = makeid();
    setMessages((oldMessages) => [{
        message: 'Входящее сообщение ' + varId2,
        ownMessage: false,
        id: varId2
      },
      ...oldMessages
    ])
  }

  useEffect(() => {
    const setIntervalFunction = () => {
      setInterval(getMessage, 3000)
    }
    setIntervalFunction()
  }, [])

  return ( < div >
    <
    ul className = 'main__chat-container' > {
      messages.map(entry => ( <
        li key = {
          entry.id
        }
        data - id = {
          entry.id
        }
        className = {
          `main__chat-message`
        } > {
          [entry.message, entry.ownMessage]
        } <
        /li>
      ))
    } <
    /ul> < /
    div > )
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render( < App / > )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>