Repository: h2oai/wave
File: ui/src/chatbot.tsx (TypeScript)

Code before:
      setUserInput(newVal)
    },
    onKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
      if (!e.shiftKey && e.key === 'Enter') {
        e.preventDefault() // Do not add an extra newline to the input text box after submission.
        submit()
      }
    },
    submit = () => {
      if (!userInput.trim()) return
      setMsgs([...msgs, { content: userInput, from_user: true }])
      wave.push()
      setUserInput('')
    },
    stopGenerating = () => { if (props.events?.includes('stop')) wave.emit(props.name, 'stop', true) },
    onLoad = React.useCallback(() => {
      if (props.events?.includes('scroll_up')) {
        wave.emit(props.name, 'scroll_up', true)
        setIsInfiniteLoading(true)
      }
    }, [props.events, props.name]),
    onDataChange = React.useCallback(() => { if (props.data) setMsgs(processData(props.data)) }, [props.data]),
    handleFeedback = (positive: B, id: I) => {
      if (props.events?.includes('feedback')) {
        setRated(rated => {
          if (rated.has(id) && rated.get(id) === positive) {
            rated.delete(id)
            wave.emit(props.name, 'feedback', { message: msgs[id].content, positive: null })
          }
          else {
            rated.set(id, positive)
            wave.emit(props.name, 'feedback', { message: msgs[id].content, positive })
          }
          return new Map(rated)
        })
      }
    }

  React.useEffect(() => {
    if (isBuf(props.data)) props.data.registerOnChange(onDataChange)
  }, [props.data, onDataChange])
  React.useEffect(() => {
    if (!props.prev_items) return
    // Perf: Assign stable ID to prevent React from recreating the entire list.
    // Currently for prev_items only, needs to be done for the rest as well.
    const newMsgs = props.prev_items.map(i => ({ ...i, id: xid() }))
    setMsgs(prev => [...newMsgs, ...prev])
    setIsInfiniteLoading(false)
    skipNextBottomScroll.current = true
  }, [props.prev_items])
  React.useLayoutEffect(() => {

Review comment (suggestion):
Wouldn't this be enough?
```suggestion
          if (rated.get(id) === positive) {
```

Code after:
        e.preventDefault() // Do not add an extra newline to the input text box after submission.
        submit()
      }
    },
    submit = () => {
      if (!userInput.trim()) return
      setMsgs([...msgs, { content: userInput, from_user: true }])
      wave.push()
      setUserInput('')
    },
    stopGenerating = () => { if (props.events?.includes('stop')) wave.emit(props.name, 'stop', true) },
    onLoad = React.useCallback(() => {
      if (props.events?.includes('scroll_up')) {
        wave.emit(props.name, 'scroll_up', true)
        setIsInfiniteLoading(true)
      }
    }, [props.events, props.name]),
    onDataChange = React.useCallback(() => { if (props.data) setMsgs(processData(props.data)) }, [props.data]),
    handlePositive = (id: I) => {
      setMsgs(messages => {
        if (messages[id]?.positive) return messages
        messages[id].positive = true
        wave.emit(props.name, 'feedback', { message: messages[id].content, positive: true })
        return [...messages]
      })
    },
    handleNegative = (id: I) => {
      setMsgs(messages => {
        if (messages[id]?.positive !== undefined && !messages[id].positive) return messages
        messages[id].positive = false
        wave.emit(props.name, 'feedback', { message: messages[id].content, positive: false })
        return [...messages]
      })
    }

  React.useEffect(() => {
    if (isBuf(props.data)) props.data.registerOnChange(onDataChange)
  }, [props.data, onDataChange])
  React.useEffect(() => {
    if (!props.prev_items) return
    // Perf: Assign stable ID to prevent React from recreating the entire list.
    // Currently for prev_items only, needs to be done for the rest as well.
    const newMsgs = props.prev_items.map(i => ({ ...i, id: xid() }))
    setMsgs(prev => [...newMsgs, ...prev])
    setIsInfiniteLoading(false)
    skipNextBottomScroll.current = true
  }, [props.prev_items])
  React.useLayoutEffect(() => {
    if (!msgContainerRef.current) return
    if (skipNextBottomScroll.current) {
      skipNextBottomScroll.current = false