WDI-SEA / project-4-issues

Open an issue to receive help on project 4 issues
0 stars 0 forks source link

useEffect resetting state with socket.io #21

Closed devinrbopp closed 2 years ago

devinrbopp commented 2 years ago

What stack are you using?

(ex: MERN(mongoose + react), DR(django + react), PEN, etc.)

MERN

What's the problem you're trying to solve?

I am storing chat messages in state. I need to use useEffect with socket.io to process incoming messages, but every time it is triggered it wipes the state, so it only contains the most recent message. I've been working on this all morning, and part of last night.

Post any code you think might be relevant (one fenced block per file)

import { useState, useEffect } from 'react'
import Message from './Message'

import { socket } from '../../App'

export default function Chat(props) {
    // state
    const [newPayload, setNewPayload] = useState({
        message: '',
        sender: props.profile.name
    })
    const [messages, setMessages] = useState([])

    const messageChangeHandler = (e) => {
        setNewPayload({... newPayload, [e.target.name]: e.target.value})
    }

    const messageSend = (e) => {
        e.preventDefault()
        if (newPayload.message) {
            socket.emit('chat message', newPayload)
            setNewPayload({
                message: '',
                sender: props.profile.name  
            })
        }
    }

    useEffect(() => {
        socket.on('broadcast', msg => {
            let oldMessages = messages
            let newMessage = [<Message key={String(Math.random())} sender={msg.sender} message={msg.message} />]
            let updatedMessages = oldMessages.concat(newMessage)
            setMessages(updatedMessages)
        })        
    }, [socket])

    return(
        <div id='chatbox'>
            <div id='messages'>
                {messages}
            </div>
            <form onSubmit={messageSend}>
                <input 
                    type="text" 
                    name="message" 
                    id="message" 
                    placeholder="Start a new message" 
                    onChange={messageChangeHandler} 
                    value={newPayload.message}
                    autoComplete='off'
                />
                <input type="submit" value="Send" />
            </form>
        </div>
    )
}

If you see an error message, post it here. If you don't, what unexpected behavior are you seeing?

No error. Unexpected behavior is that only the most recent message will render/get stored in state. Previous messages are lost.

What is your best guess as to the source of the problem?

I looks like it's because the useEffect is wiping the state, but I have no idea why that would happen.

What things have you already tried to solve the problem?

I've tried moving functions to different components, moving things in and out of the useEffect, creating additional temp variables, no luck. The one thing that worked was removing the useEffect entirely, but that causes duplicate socket.on triggers that grow exponentially and slow the app down after 5 messages.

timmshinbone commented 2 years ago

So, first thing I noticed is that you're storing components in an array in state. Instead, you should be storing the data for those components, then mapping over that data and producing one message component for each item in the array. I also wanted to remind you of this syntax we used in the hooks lesson for updating state based on the previous state, it looked like this:

  const addSprinkles = () => {
    setDonut(prevDonut => prevDonut + ' with sprinkles')
  }

That syntax might possibly be better used with the spread operator in your case since you're using an array, just to continue with my example:

  const addSprinkles = () => {
    setDonut(prevDonut => ...prevDonut += ' with sprinkles')
  }

That's just another thing you can attempt in the case that using the first syntax doesn't solve the problem. So, steps are as follows:

  1. refactor the component to store message data in state, map over that array, and produce one Message component each
  2. refactor how useEffect utilizes the setMessages hook, to factor in the previous state and add to it so you can avoid lifecycle issues.
devinrbopp commented 2 years ago

applying this now, will report back!

devinrbopp commented 2 years ago
useEffect(() => {
        socket.on('broadcast', data => {
            console.log(messagesData)
            let previousData = messagesData
            let newData = previousData.concat([data])
            setMessagesData(newData)
            // console.log('THESE ARE THE OLD MESSAGES: ', messages)
            // let oldMessages = messages
            // let newMessage = [<Message key={String(Math.random())} sender={data.sender} message={data.message} />]
            // let updatedMessages = oldMessages.concat(newMessage)
            // setMessages([...updatedMessages])
        })        
    }, [socket])

const messages = messagesData.map((data, i) => {
    return <Message key={i} sender={data.sender} message={data.message} />
})

Divided it up and renamed. I stuck with this syntax because the spread operator wasn't working. What's scaring me here is the console.log(messagesData), which returns a blank array every time. So it's not that my setMessagesData is overwriting it, but that it's already emptied out every time I get to it. Does that make sense?

devinrbopp commented 2 years ago

It's still rendering the most recent message across all open client sockets, which is good!

Weirdly enough, if I put a console.log above socket.on, it only prints once, when the page first renders, even though the socket.on listener is getting triggered every time a message is sent.

TaylorDarneille commented 2 years ago

Is this resolved?

devinrbopp commented 2 years ago

Not yet, Timm and I are working on it!

devinrbopp commented 2 years ago

Still working on this w/o Timm, will post an update or tag you when something has changed.

devinrbopp commented 2 years ago

@TaylorDarneille I posted on stackoverflow, if you're bored and have any ideas let me know please~!

https://stackoverflow.com/questions/70671831/react-socket-io-not-displaying-latest-message-passed-down-as-prop