1

I'm working on a web app that reads sensor data to gauge objects via socket.io.

The function addNewGauge() adds an element to the component's array of gauges, but I also want to install a socket.io listener for that element.

The addNewGauge() function works, and the listener is picking up the event, which I know because the error below occurs for every socket event:

TypeError: Cannot read property 'value' of undefined occurs for each socket event I send.

I'm assuming this is because it can't read the array index during the listener? How do I update the state of that specific element in the state every time?

import React from 'react'
import socketIOClient from "socket.io-client"

class WidgetContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      //Initial array of gauges
      gauges: [{
        index: 0,
        sensorType: 'temperature',
        units: "Fahrenheit"
      }],
      //Endpoint for socket data
      endpoint: 'http://127.0.0.1:4200'
    }
    this.addNewGauge = this.addNewGauge.bind(this)
  }

  componentDidMount() {
    const {endpoint} = this.state;
    //Fire up the socket when container is mounted
    this.widget_socket = socketIOClient(endpoint)
  }

  addNewGauge() {
    this.setState(state => {
      // ensure next gauge has a unique index
      const nextIndex = state.gauges.length;
      const gaugeList = state.gauges.concat({
        index: nextIndex,
        sensorType: 'temperature',
        units: "Fahrenheit"
      });
      // Set up a listener for that gauge's sensor type
      //BROKEN
     this.widget_socket.on(gaugeList[nextIndex].sensorType, (data) => {
        //Update state with new sensor value
        let newState = update(this.state.gauges, {
          nextIndex: {
            value: {
              $set: data
            }
          }
        })
        this.setState(newState);
      })

      return {
        gauges: gaugeList
      };
    });
  }

  render() {
    let gaugeList = this.state.gauges.map((gauge) =>
      <Gauge 
      key = {gauge.index}
      // Specify gauge properties from our saved state
      value = {gauge.value}
      units = {gauge.units}
      />
    );

    return ( 
            <div>
               {gaugeList}
               <Button key="addButton" 
                onClick={this.addNewGauge}
               >Add gauge</Button>
            </div>
    );
  }
}

export default WidgetContainer

5
  • Your use of this.setState is pretty confusing, most of the logic inside the function passed to setState could be placed outside and would enhance readability. You're also trying to set the state inside of an existing call to set the state which I'm sure is bad scoping and why the second setState in the socket listener isnt working Commented Jan 23, 2021 at 19:08
  • What data does this.widget_socket.on return on an event? And where is the update function coming from? I would check the value of newState and confirm that its the value you want, maybe the second setState is working but you're setting bad data into the state Commented Jan 23, 2021 at 19:10
  • 1
    Thanks @bryanstevens314 - I'm currently trying a separate callback, but the reason it's in the setState is so it can capture the index of the gauge that was added. That index is only in the scope of the state modification Commented Jan 23, 2021 at 19:30
  • this might be a noob question but where is the new state coming from on your call to this.setState? Genuinely curious I use function based components and hooks for everything lately its been been at least a year since I've used react with classes/ setState but I cant piece together where the new state state is coming from in your example Commented Jan 24, 2021 at 21:30
  • In the socket handler (_socket.on..), newState just comes a few lines up in "let newState =". That Update() function is supposed to make a copy of the gauges array, and modify the value in nextIndex. The first this.setState is harder to read, but it's just one big arrow function. We pass in state, create a two variables using "let", create a (broken) handler, and return the gaugeList variable from earlier. Commented Jan 27, 2021 at 4:30

1 Answer 1

1

I seem to have found a solution by

  1. Adding a separate callback and binding to the class
  2. Not using "update()"

When the state is modified, this callback for the socket works great:

            let widgetCallback = (data, index) => {
            let newState = this.state
            newState.gauges[index].value = data;
            this.setState(newState)
        }
        // bind the callback to the container
        widgetCallback.bind(this)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.