var RECEIVED_ROOMS = "RECEIVED_ROOMS";
var SET_ACTIVE_ROOM = "SET_ACTIVE_ROOM";
var RECEIVED_MESSAGE = "RECEIVED_MESSAGE";
var SAY = "SAY";
var SET_USERNAME = "SET_USERNAME";
var CREATE_ROOM = "CREATE_ROOM";
var server = new Firebase("https://tocode-redux-chat.firebaseio.com/");
server.child("rooms").on("value", function(snapshot) {
store.dispatch(actions.receivedRooms(snapshot.val()));
});
var initialState = {
rooms: {
all: {},
},
messages: {
activeRoom: undefined,
activeRoomId: undefined,
all: [],
},
username: "Anonymous",
};
var actions = {
receivedRooms: (res) => ({ type: RECEIVED_ROOMS, payload: res }),
setActiveRoom: (room_id) => ({ type: SET_ACTIVE_ROOM, payload: room_id }),
createRoom: (room) => ({ type: CREATE_ROOM, payload: room }),
receivedMessage: (text) => ({ type: RECEIVED_MESSAGE, payload: text }),
say: (text) => ({ type: SAY, payload: { text: text, from: store.getState().username }}),
setUsername: (name) => ({ type: SET_USERNAME, payload: name }),
};
function new_message(snapshot) {
var text = snapshot.val();
store.dispatch(actions.receivedMessage(text));
}
function rooms(state, action) {
switch(action.type) {
case RECEIVED_ROOMS:
return Object.assign({}, state, { all: action.payload } );
case CREATE_ROOM:
setTimeout(function() {
server.child('rooms').push({ name: action.payload, messages: [] });
}, 0);
return state;
default:
return state;
}
}
function messages(state, action) {
switch(action.type) {
case RECEIVED_MESSAGE:
return Object.assign({}, state, { all: state.all.concat(action.payload)});
case SAY:
setTimeout(function() {
state.activeRoom.push( action.payload );
}, 0);
return state;
case SET_ACTIVE_ROOM:
if ( state.activeRoom ) {
state.activeRoom.off();
}
var activeRoom = server.child('rooms').child(action.payload).child('messages');
setTimeout(function() {
activeRoom.on('child_added', new_message);
}, 0);
return Object.assign({}, state, { activeRoom: activeRoom, activeRoomId: action.payload, all: [] });
default:
return state;
}
}
function userinfo(state, action) {
switch(action.type) {
case SET_USERNAME:
return action.payload;
default:
return state;
}
}
function myApp(state = initialState, action) {
return {
...state,
rooms: rooms(state.rooms, action),
messages: messages(state.messages, action),
username: userinfo(state.username, action)
};
}
var store = Redux.createStore(myApp);
var RoomList = React.createClass({
render: function() {
var rooms = this.props.rooms;
var setActiveRoom = this.props.setActiveRoom;
return <ul className="nav nav-pills nav-stacked">
{_.map(rooms, function(info) {
var cls = this.props.activeRoomId === info.id ? "active" : "";
return <li className={cls} key={info.id} ><a onClick={() => this.props.setActiveRoom(info.id)}>{info.name}</a></li>
}, this)}
</ul>
},
propTypes: {
rooms: React.PropTypes.arrayOf(React.PropTypes.shape({
name: React.PropTypes.string,
id: React.PropTypes.string
})),
setActiveRoom: React.PropTypes.func
}
});
var Message = React.createClass({
render: function() {
return <dl className="dl-horizontal">
<dt>{this.props.from}</dt>
<dd>{this.props.text}</dd>
</dl>
}
});
var ChatWindow = React.createClass({
getInitialState: function() {
return { currentText: "" };
},
setText: function(e) {
this.setState({currentText: e.target.value});
},
say: function(e) {
e.preventDefault();
this.props.say(this.state.currentText);
this.setState({currentText: "" });
},
render: function() {
return <div>
{_.map(this.props.messages, function(msg) {
return <Message {...msg} />
})}
<form onSubmit={this.say} className="form-inline">
<input type="text" value={this.state.currentText} onChange={this.setText} />
<input type="submit" value="Say" />
</form>
</div>
}
});
var UserDetails = React.createClass({
getInitialState: function() {
return {
username: this.props.initialUsername
}
},
setUsername: function(e) {
this.setState({username: e.target.value});
},
notifyNewUsername: function(e) {
e.preventDefault();
this.props.setUsername(this.state.username);
},
revert: function() {
this.setState({
username: this.props.initialUsername
});
},
render: function() {
return <form onSubmit={this.notifyNewUsername} className="form-inline">
<div className="form-group">
<label htmlFor="username">Username:</label>
<input className="form-control" id="username" type="text" value={this.state.username} onChange={this.setUsername} />
</div>
<button type="submit" className="btn btn-default">Save</button>
<button type="submit" className="btn btn-default" onClick={this.revert}>Revert</button>
</form>
}
});
var _App = React.createClass({
getInitialState: function() {
return this.select(store.getState());
},
setUsername: function(username) {
store.dispatch(actions.setUsername(username));
},
componentDidMount: function() {
this.unsubscribe = store.subscribe(this.storeDataChanged);
},
select: function(state) {
return {
username: state.username,
rooms: _.map(Object.keys(state.rooms.all), function(room_id) {
return { id: room_id, name: state.rooms.all[room_id].name };
}),
messages: state.messages.all,
activeRoomId: state.messages.activeRoomId,
}
},
storeDataChanged: function() {
this.setState(this.select(store.getState()));
},
componentWillUnmount: function() {
this.unsubscribe();
},
setActiveRoom: function(room_id) {
store.dispatch(actions.setActiveRoom(room_id));
},
say: function(text) {
store.dispatch(actions.say(text));
},
render: function() {
return <div className="container">
<div className="row">
<div className="col-xs-12">
<UserDetails initialUsername={this.state.username} setUsername={this.setUsername} />
</div>
</div>
<div className="row">
<div className="col-xs-4">
<RoomList rooms={this.state.rooms} setActiveRoom={this.setActiveRoom} activeRoomId={this.state.activeRoomId} />
</div>
<div className="col-xs-8">
{ this.state.activeRoomId ?
<ChatWindow messages={this.state.messages} say={this.say} /> :
false
}
</div>
</div>
</div>
}
});
React.render(<_App />, document.querySelector('main'));
View Compiled