简体   繁体   中英

Using WebSockets with ReactJS, do not know how to render server message on client

I'm trying to integrate Socket.IO with my application. I'm building a real time data tool. For now, I just want to see constant communication between my server and the client. I want to send a simple message and log it on the console every 5 seconds.

Here is my server.js:

const path = require('path');
const express = require('express');
const http = require("http");
const socketIo = require("socket.io");

const DIST_DIR = path.join(__dirname, 'public/');
const PORT = 3000;

const app = express();
app.use(express.static(DIST_DIR));

const server = http.createServer(app);

//wire up ExpressJS server to Socket.IO
const io = socketIo(server);

io.on("connection", (socket) => {
  console.log("Connected to client"), setInterval(
    () => socket.emit('testEvent', "Message from server sent"),
    5000
  );
  socket.on("disconnect", () => console.log("Client disconnected"));
});

app.get('*', function(req, res) {
    res.sendFile(path.join(DIST_DIR, "index.html"));
});

server.listen(PORT, () => console.log("server listening on port " + PORT));

On the client, I have (I'm using DataMaps jQuery plugin):

import propTypes from 'prop-types';
import React, { Component } from 'react';
import Datamaps from 'datamaps';
import socketIOClient from "socket.io-client";

export default class Datamap extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      response: false,
      endpoint: "http://127.0.0.1:3000"
    };
    window.addEventListener('resize', this.resize);
  }

  resize() {
    if (this.map) {
      this.map.resize();
    }
  }

  //creates a new connection to Socket.IO server
  componentDidMount() {
    const { endpoint } = this.state;
    const socket = socketIOClient(endpoint);
    socket.on("testEvent", data => this.setState({ response: data }));
    this.drawMap();
  }

  componentWillReceiveProps() {
    this.clear();
  }

  componentDidUpdate() {
    this.drawMap();
  }

  componentWillUnmount() {
    this.clear();
    window.removeEventListener('resize', this.resize);
  }

  clear() {
    const container = this.refs.container;

    for (const child of Array.from(container.childNodes)) {
      container.removeChild(child);
    }
  }

  fadingBubbles(layer, data) {
       //irrelevant, left out
  }


  drawMap() {
    var map = new Datamaps({
      ...this.props,
      element: this.refs.container,
      projection: 'mercator',
      responsive: true
    });

    if (this.props.arc) {
      map.arc(this.props.arc, this.props.arcOptions);
    }

    if (this.props.bubbles) {
      map.bubbles(this.props.bubbles, this.props.bubbleOptions);
    }

    if (this.props.graticule) {
      map.graticule();
    }

    if (this.props.labels) {
      map.labels();
    }

    this.map = map;
    this.map.addPlugin('fadingBubbles', this.fadingBubbles.bind(this.map));
  }

  drawBubbles = () => {
    const { response } = this.state;
    var data = [
      {
        "latitude": "28.014067",
        "longitude": "-81.728676"
      }, {
        "latitude": "40.750793",
        "longitude": "-73.989525",
        "magnitude": 3
      }
    ];
    this.map.fadingBubbles(data);
    //console.log({ response });
  }

  render() {
    const style = {
      position: 'relative',
      width: '100%',
      height: '100%'
    };

    return (<div>
      <button onClick={this.drawBubbles}>Draw Fading Bubbles</button>
      <div ref="container" style={style}></div>
    </div>);
  }
}

Datamap.propTypes = {
  arc: propTypes.array,
  arcOptions: propTypes.object,
  bubbleOptions: propTypes.object,
  bubbles: propTypes.array,
  graticule: propTypes.bool,
  labels: propTypes.bool
};

So, currently I have a button. Eventually the function drawBubbles will retrieve lat and long information from the server and feed it into the map to display corresponding bubbles, but for now I just want the messages and I'll be able to figure that out. When the button is pressed, the message is printed on the client's console, which is right. But I want to get rid of that button and be able to see the messages every 5 seconds (so without an onclick action), as written in my server. I'm not entirely sure how component mounting works, but when I put merely the function call this.drawBubbles; outside of the return inside the render() function, I got the following warning (and nothing printed on the console):

Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.

Please check the code for the Datamap component.

Could someone explain how I can get this to work? Perhaps how components work in the context of my app could be of use to me.

I suspect it's working, but you don't like the warning that you're updating state when unmounted (I hate it too, because it hints that things are not managed as expected). I like this note for componentWillUnmount() :

Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().

Perhaps consider initializing your socket.io client in its own instance, living outside of Datamap so that you may decouple socket.io activity from the lifetime of the Datamap component.

Here's a trivial example you can extend in your Datamap implementation:

const socket = socketIOClient('http://127.0.0.1:3000');
let listener = null;
socket.on('testEvent', (data) => {
  if (listener) {
    listener.handleTestEvent({ response: data });
  }
});

export class Datamap extends React.Component {
  componentWillUnmount() {
    listener = null;
  }

  componentDidMount() {
    listener = {
      handleTestEvent: (ev) => console.log(ev.response),
    };
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM