Home > database >  React renders repeatedly on event
React renders repeatedly on event

Time:01-19

I'm using sockets in my website and there's an event where one user can send a word to the server, which emits (art-addpic) an image URL corresponding to that word to everyone, but only the user with isArtist=true gets to respond to the event. The artist's page is supposed to update an existing list of image URLs (optionImages) with the received URL once. But when the event is received, all images in the list are replaced by the received URL. Furthermore, the component rendering the list of images ArtBoard is not re-rendered with updated URLs. I'm new to React. Where am I going wrong?

I've checked the server and the event art-addpic is broadcasted only once.

Arena.js: (The webpage where this happens):

import React, { useEffect, useState } from "react";
import Leaderboard from "../comps/Leaderboard";
import { io } from "socket.io-client";
import Service from "../Service";
import DetBoard from "../comps/DetBoard";
import ArtBoard from "../comps/ArtBoard";
const username = "Nick"
const roomkey="abc"
let userid;
if(localStorage.getItem('userid')){
    userid = localStorage.getItem('userid')
}
else{
    userid = Service.makeid(5);
    localStorage.setItem('userid', userid);
}
function useForceUpdate(){
    const [value, setValue] = useState(0); // integer state
    return () => setValue(value => value   1); // update the state to force render
}
// const [userid,setUserId] = 
const socket = io('http://localhost:3001', {query:"username=" username "&roomkey=" roomkey "&userid=" userid});
const Arena = (props)=>{
    const [isArtist, setIsArtist] = useState(false);
    const [focusImage, setFocusImage] = useState('https://i.imgur.com/61HsZCU.jpeg')
    const [players, setPlayers] = useState([]);
    const [optionImages, setOptionImages] = useState([
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg'
    ])
    useEffect(()=>{
        socket.on('connect',()=>{
            console.log("connected")
        })
        socket.on('players', (data)=>{
            data = JSON.parse(data)
            console.log(data)
            setPlayers(data)
        })
        socket.on('artist', (data)=>{
            if(data===userid){
                console.log('You are an artist, Mr White.')
                setIsArtist(true);
            }
            else{
                setIsArtist(false);
            }
        })    
        socket.on('art-addpic', (data)=>{
            data = JSON.parse(data)
            console.log(data)
            let tempOps =optionImages;
            tempOps.splice(0, 1);
            tempOps.push(data.url)
            console.log(tempOps)
            setOptionImages(tempOps);
        })
    }, [
        optionImages
    ]);
    if(isArtist){
        return(
            <div>
            <Leaderboard players={players}></Leaderboard>
            {/* <ArtBoard></ArtBoard> */}
            <ArtBoard socket={socket} focusImage={focusImage} optionImages={optionImages} setOptionImages={setOptionImages}/>         
        </div>
        );
    }
    else{
        return (
            <div>
            <Leaderboard players={players}></Leaderboard>
            {/* <ArtBoard></ArtBoard> */}
            <DetBoard socket={socket} focusImage={focusImage}/>         
        </div>
        );
    }
}
export default Arena;

CodePudding user response:

You've at least a few issues:

  1. No clean up function returned from the useEffect hook to unsubscribe the socket connections, so they remain open.
  2. optionImages state mutations.
  3. Updating the optionImages state retriggers the useEffect callback which creates more subscriptions.

Hook Code

useEffect(() => {
  socket.on('connect', () => {
    console.log("connected");
  });

  socket.on('players', (data) => {
    data = JSON.parse(data);
    console.log(data);
    setPlayers(data);
  });

  socket.on('artist', (data) => {
    if (data === userid) {
      console.log('You are an artist, Mr White.');
      setIsArtist(true);
    } else {
      setIsArtist(false);
    }
  });

  socket.on('art-addpic', (data) => {
    data = JSON.parse(data);
    console.log(data);
    let tempOps = optionImages; // (2) tempOps is reference to optionImages state
    tempOps.splice(0, 1);   // (2) mutation!
    tempOps.push(data.url); // (2) mutation!
    console.log(tempOps);
    setOptionImages(tempOps); // (2,3) saved state reference back into state
  });

  // (1) missing cleanup function
}, [optionImages]); // (3) state updated in hook

From what I can tell, the main issue is with the 'art-addpic' event. It seems like you want to remove the first element from the optionImages state and add a new URL to the end.

If this is the case then I have the following suggestions:

  1. Return a cleanup function to unsubscribe the socket connections.
  2. Remove all useEffect hook dependencies so the hook run once when the component mounts to establish the socket subscriptions, and clean them up when unmounting.
  3. Use a functional state update for optionImages to remove the state as an external dependency.

Hook Code

useEffect(() => {
  socket.on('connect', () => {
    console.log("connected");
  });

  socket.on('players', (data) => {
    const parsedData = JSON.parse(data);
    console.log(parsedData);
    setPlayers(parsedData);
  });

  socket.on('artist', (data) => {
    setIsArtist(data === userid);
  });

  socket.on('art-addpic', (data) => {
    const parsedData = JSON.parse(data);
    console.log(parsedData);

    setOptionImages(optionImages => 
      // Shallow copy into array, append URL, slice & keep last 4 elements
      [...optionImages, parsedData.url].slice(-4),
    );
  });

  return () => {
    socket.removeAllListeners();
  }
}, []);

useEffect(() => {
  if (isArtist) {
    console.log('You are an artist, Mr White.');
  }
}, [isArtist]);
  •  Tags:  
  • Related