11

Not sure if the issue is how I have my sockets setup - or if I am incorrectly trying to render the data with React.

I can successfully pull in data with my socket - yet it doesn't live update state when new data is posted to the server. My intention is for the state in React to automatically render new data, which is always live because of the socket connection.

Here is my client app that gets messages from the server and renders them:

var Chat = React.createClass({
  getInitialState: function() {
    return {
      messages: null
    }
  },
  componentWillMount: function(){
    var self = this;
    socket.emit('getMessages');
    socket.on('serverMessages', function (data) {
      self.setState({messages: data})
    });
  },
  render: function() {
    var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
    return (
      <div className="jumbotron">
        { messages }
        <MessageForm submitMessage={this.submitMessage}/>
      </div>
      );
  }
});

Just in case here is my server code that emits data:

io.on('connection', function (socket) {

  socket.on('getMessages', function (data) {
    Message.find(function(err, messages){
      socket.emit('serverMessages', messages);
    })
  });

});
fresh5447
  • 1,242
  • 3
  • 14
  • 27

4 Answers4

6

As of right now, you're "just" grabbing data from the server once the component has been loaded. To get something a bit more "real time" you'll want to either ping the server with the same emit statement you specified regularly (which defeats the point of using websockets, really, you could use long-polling) or have the server regularly send new data to all clients.

You can do EITHER:

A) Client side: "Polling" for information [Not Ideal]

Note: I initially put this in my answer because I saw the OP was "polling" when the controller was loaded. I didn't click on that this might be because the controller may not be loaded with the websocket so sending data on connect might not work here. My bad.

Replace socket.emit('getMessages') with something that will "poll" the websocket regularly for data:

setInterval(function () {
    socket.emit('getMessages')
}, 10000); /* Request data from the socket every 10 seconds */

OR

B) Server side: Send new data as it becomes available. [Best Way]

Track all clients via a clients array and delete them from it when their session ends.

var clients = [];

io.on('connection', function (socket) {
    clients.push(socket);

    socket.on('end', function () {
        // Could also splice the array below, but it still works.
        delete clients[clients.indexOf(socket)];
    });


    /* Previous logic for server goes here */
});

Run this code when you need to push new messages from the database/data storage:

for (var i in clients) {
    clients[i].emit('serverMessages', /* messages object */);
}
megubyte
  • 1,549
  • 9
  • 15
  • Regarding option A: It just doesn't sound right to have to poll on the client.. Doesn't a client socket connected to a server socket create a live stream of data? – fresh5447 Apr 21 '16 at 18:00
  • Option B is the best way - i thought I mentioned that but obviously not. I released you were polling initially but in that way it makes sense in case your component isn't loaded when you connect to the websocket or so :P – megubyte Apr 22 '16 at 07:07
  • Using broadcast/emit and on namespaces or rooms might be safer (and easier) than keeping track of clients (`clients` array) yourself. Once you start running multiple node instances, that `clients` array will only contain the instances connected to that particular host. Same is true by default with socket.io, but it does have officially recommended ways of dealing with multiple hosts https://socket.io/docs/using-multiple-nodes/ – tybro0103 Nov 20 '17 at 16:57
4

Your server code is only firing upon initial socket connection.

Server:

socket.on('getMessages', function (data) {
  Message.find(function(err, messages){
    socket.emit('serverMessages', messages);
  })
});

Client:

var Chat = React.createClass({
  getInitialState: function() {
    return {
      messages: null
    }
  },
  componentWillMount: function(){
    var self = this;
    socket.emit('getMessages');
    socket.on('serverMessages', function (data) {
      self.setState({messages: data})
    });
  },
  render: function() {
    var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
    return (
      <div className="jumbotron">
        { messages }
      </div>
      );
  }
});

Based on naming convention, it also appears that your Message.find() is pulling a single message. I would recommend clarifying the labeling to match cardinality.

kwcto
  • 3,494
  • 2
  • 26
  • 33
  • Code was hastily written in-browser and not tested, so I apologize if there are typos/logic errors. The key flaw in the original example was the missing event handler for `getMessages` – kwcto Apr 15 '16 at 05:58
  • Thanks @kwcto - I have implemented your feedback but it still behaves the same. New messages only appears upon page refresh. ( I have updated the question to reflect the new implementation) – fresh5447 Apr 15 '16 at 06:33
4

Try this:

var Chat = React.createClass({
  getInitialState: function() {
    return {
      messages: null
    }
  },
  componentWillMount: function(){
    var self = this;
    socket.emit('getMessages');
    socket.on('serverMessages', function (data) {
      self.setState({messages: data})
    });
  },
  render: function() {
    var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
    return (
      <div className="jumbotron">
        { messages }
        <MessageForm submitMessage={this.submitMessage}/>
      </div>
      );
  }
});
dwww
  • 4,888
  • 3
  • 13
  • 12
1

Could it be possible its due to the componentWillMount lifecycle method? Could you try the componentDidMount instead.

It looks like render will see the state update but only gets executed once despite the state change according to facebook.

Jack7
  • 1,310
  • 12
  • 16