Create a gist now

Instantly share code, notes, and snippets.

Using WebSockets with Reflux and React

WebSockets + Reflux + React

Using WebSockets, React and Reflux together can be a beautiful thing, but the intial setup can be a bit of a pain. The below examples attempt to offer one (arguably enjoyable) way to use these tools together.

Overview

This trifect works well if you think of things like so:

  1. Reflux Store: The store fetches, updates and persists data. A store can be a list of items or a single item. Most of the times you reach for this.state in react should instead live within stores. Stores can listen to other stores as well as to events being fired.
  2. Reflux Actions: Actions are triggered by components when the component wants to change the state of the store. A store listens to actions and can listen to more than one set of actions.
  3. React Component: A react component listens to changes in a store and re-renders accordingly. A component can fire actions which then in turn update a store.
  4. WebSockets: WebSockets allow an event driven approach to frontend programming where the browser and server can just send out and listen to events. WebSocket events should be listened to and triggered in stores and no where else.

Notes

  • Don't call methods on a store, instead use actions.
  • Prefer where possible to have a parent component that passes down data as props instead of listening to stores everywhere.
  • All examples are using ES6 and JSX. You'll need a compile stage to run the examples. I recommend WebPack with the JSX and babel plugins.
var Reflux = require('reflux');
module.exports = Reflux.createActions([
'delete',
// 'update', 'flag', 'report', 'search', etc...
]);
var _ = require('lodash');
var React = require('react');
var UserActions = require('./UserActions');
module.exports = React.createClass({
delete(e) {
e.preventDefault();
UserActions.delete(this.props.user.id);
},
render() {
return (
<tr>
<td>{this.props.user.name}</td>
<td><button onClick={this.delete}>Delete</button></td>
</tr>
);
}
});
var _ = require('lodash');
var React = require('react');
var Reflux = require('reflux');
var UserStore = require('./UserStore');
module.exports = React.createClass({
mixins: [
Reflux.connect(UserStore, 'userStore'),
],
render() {
var store = this.state.userStore;
if (store.loading) {
return <div className='alert alert-info'>Loading...</div>;
}
if (store.errorMessage) {
return <div className='alert alert-danger'>{store.errorMessage}</div>;
}
var users = _.map(store.users, function (user) {
return <UserItem user={user} key={user.id} />;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{users}
</tbody>
</table>
);
}
});
var _ = require('lodash');
var Reflux = require('reflux');
var io = require('socket.io-client');
var socket = io();
var UserActions = require('./UserActions');
var initialState = {
loading: false,
errorMessage: '',
users: [],
};
module.exports = Reflux.createStore({
listenables: [UserActions],
init() {
socket.on('connect', () => {
this.fetchUsers();
});
},
getInitialState() {
return this.state = initialState;
},
fetchUsers() {
this.state.errorMessage = '';
this.state.loading = true;
socket.emit('users:all', this.updateUsers.bind(this));
},
updateUsers(err, users) {
this.state.errorMessage = '';
this.state.loading = false;
if (err) {
console.error('Error updating users:', err);
this.state.errorMessage = err;
this.trigger(this.state);
return;
}
this.state.users = users;
this.trigger(this.state);
},
onDelete(userID) {
var user = _.find(this.state.users, { id: userID });
// Indicate the user is being updated.
this.state.errorMessage = '';
this.state.loading = true;
this.trigger(this.state);
socket.emit(
'users:delete',
userID,
function (err) {
// The callback has been fired so indicate the request has finished.
user.loading = false;
if (err) {
console.error('Error deleting user:', err);
this.state.errorMessage = err;
this.trigger(this.state);
return;
}
// TODO: Delete the user from the list of users here...
this.trigger(this.state);
}.bind(this)
);
},
});
@caruccio

Forgive my ignorance, but shouldn't UserStore.js:48 be delete(userId), or UserItem.jsx:9 call onDelete()? Does Reflux known how to map an action to doAction?

@grenhall
grenhall commented Aug 8, 2016

Yes Reflux knows. You should use onAction in your store. Sorry for the late reply.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment