Skip to content

Commit 00ae1c2

Browse files
committed
20-Realtime Chat with React and Firebase Database
1 parent 3061e4b commit 00ae1c2

File tree

2 files changed

+274
-7
lines changed

2 files changed

+274
-7
lines changed

src/components/Firebase/firebase.js

+14
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ class Firebase {
1515
constructor() {
1616
app.initializeApp(config);
1717

18+
/* Helper */
19+
20+
this.serverValue = app.database.ServerValue;
1821
this.emailAuthProvider = app.auth.EmailAuthProvider;
22+
23+
/* Firebase APIs */
24+
1925
this.auth = app.auth();
2026
this.db = app.database();
2127

28+
/* Social Sign In Method Provider */
29+
2230
this.googleProvider = new app.auth.GoogleAuthProvider();
2331
this.facebookProvider = new app.auth.FacebookAuthProvider();
2432
this.twitterProvider = new app.auth.TwitterAuthProvider();
@@ -89,6 +97,12 @@ class Firebase {
8997
user = uid => this.db.ref(`users/${uid}`);
9098

9199
users = () => this.db.ref('users');
100+
101+
// *** Message API ***
102+
103+
message = uid => this.db.ref(`messages/${uid}`);
104+
105+
messages = () => this.db.ref('messages');
92106
}
93107

94108
export default Firebase;

src/components/Home/index.js

+260-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,271 @@
1-
import React from 'react';
1+
import React, { Component } from 'react';
22
import { compose } from 'recompose';
33

4-
import { withAuthorization, withEmailVerification } from '../Session';
4+
import {
5+
AuthUserContext,
6+
withAuthorization,
7+
withEmailVerification,
8+
} from '../Session';
9+
import { withFirebase } from '../Firebase';
10+
11+
class HomePage extends Component {
12+
constructor(props) {
13+
super(props);
14+
15+
this.state = {
16+
loading: false,
17+
users: null,
18+
};
19+
}
20+
21+
componentDidMount() {
22+
this.setState({ loading: true });
23+
this.props.firebase.users().on('value', snapshot => {
24+
this.setState({
25+
users: snapshot.val(),
26+
loading: false,
27+
});
28+
});
29+
}
30+
31+
componentWillUnmount() {
32+
this.props.firebase.users().off();
33+
}
34+
35+
render() {
36+
const { users, loading } = this.state;
37+
38+
return (
39+
<div>
40+
<h1>Home Page</h1>
41+
<p>The Home Page is accessible by every signed in user.</p>
42+
43+
<Messages users={users} usersLoading={loading} />
44+
</div>
45+
);
46+
}
47+
}
48+
49+
class MessagesBase extends Component {
50+
constructor(props) {
51+
super(props);
52+
53+
this.state = {
54+
text: '',
55+
loading: false,
56+
messages: [],
57+
limit: 5,
58+
};
59+
}
60+
61+
componentDidMount() {
62+
this.onListenForMessages();
63+
}
64+
65+
onListenForMessages = () => {
66+
this.setState({ loading: true });
67+
68+
this.props.firebase
69+
.messages()
70+
.orderByChild('createdAt')
71+
.limitToLast(this.state.limit)
72+
.on('value', snapshot => {
73+
const messageObject = snapshot.val();
74+
75+
if (messageObject) {
76+
const messageList = Object.keys(messageObject).map(key => ({
77+
...messageObject[key],
78+
uid: key,
79+
}));
80+
81+
this.setState({
82+
messages: messageList,
83+
loading: false,
84+
});
85+
} else {
86+
this.setState({ messages: null, loading: false });
87+
}
88+
});
89+
};
90+
91+
componentWillUnmount() {
92+
this.props.firebase.messages().off();
93+
}
94+
95+
onChangeText = event => {
96+
this.setState({ text: event.target.value });
97+
};
98+
99+
onCreateMessage = (event, authUser) => {
100+
this.props.firebase.messages().push({
101+
text: this.state.text,
102+
userId: authUser.uid,
103+
createdAt: this.props.firebase.serverValue.TIMESTAMP,
104+
});
105+
106+
this.setState({ text: '' });
107+
108+
event.preventDefault();
109+
};
5110

6-
const HomePage = () => (
7-
<div>
8-
<h1>Home Page</h1>
9-
<p>The Home Page is accessible by every signed in user.</p>
10-
</div>
111+
onEditMessage = (message, text) => {
112+
this.props.firebase.message(message.uid).set({
113+
...message,
114+
text,
115+
editedAt: this.props.firebase.serverValue.TIMESTAMP,
116+
});
117+
};
118+
119+
onRemoveMessage = uid => {
120+
this.props.firebase.message(uid).remove();
121+
};
122+
123+
onNextPage = () => {
124+
this.setState(
125+
state => ({ limit: state.limit + 5 }),
126+
this.onListenForMessages,
127+
);
128+
};
129+
130+
render() {
131+
const { users, usersLoading } = this.props;
132+
const { text, messages, loading } = this.state;
133+
134+
return (
135+
<AuthUserContext.Consumer>
136+
{authUser => (
137+
<div>
138+
{!loading && messages && (
139+
<button type="button" onClick={this.onNextPage}>
140+
More
141+
</button>
142+
)}
143+
144+
{loading && usersLoading && <div>Loading ...</div>}
145+
146+
{users && messages && (
147+
<MessageList
148+
messages={messages.map(message => ({
149+
...message,
150+
user: users[message.userId],
151+
}))}
152+
onEditMessage={this.onEditMessage}
153+
onRemoveMessage={this.onRemoveMessage}
154+
/>
155+
)}
156+
157+
{!messages && <div>There are no messages ...</div>}
158+
159+
<form
160+
onSubmit={event =>
161+
this.onCreateMessage(event, authUser)
162+
}
163+
>
164+
<input
165+
type="text"
166+
value={text}
167+
onChange={this.onChangeText}
168+
/>
169+
<button type="submit">Send</button>
170+
</form>
171+
</div>
172+
)}
173+
</AuthUserContext.Consumer>
174+
);
175+
}
176+
}
177+
178+
const MessageList = ({
179+
messages,
180+
onEditMessage,
181+
onRemoveMessage,
182+
}) => (
183+
<ul>
184+
{messages.map(message => (
185+
<MessageItem
186+
key={message.uid}
187+
message={message}
188+
onEditMessage={onEditMessage}
189+
onRemoveMessage={onRemoveMessage}
190+
/>
191+
))}
192+
</ul>
11193
);
12194

195+
class MessageItem extends Component {
196+
constructor(props) {
197+
super(props);
198+
199+
this.state = {
200+
editMode: false,
201+
editText: this.props.message.text,
202+
};
203+
}
204+
205+
onToggleEditMode = () => {
206+
this.setState(state => ({
207+
editMode: !state.editMode,
208+
editText: this.props.message.text,
209+
}));
210+
};
211+
212+
onChangeEditText = event => {
213+
this.setState({ editText: event.target.value });
214+
};
215+
216+
onSaveEditText = () => {
217+
this.props.onEditMessage(this.props.message, this.state.editText);
218+
219+
this.setState({ editMode: false });
220+
};
221+
222+
render() {
223+
const { message, onRemoveMessage } = this.props;
224+
const { editMode, editText } = this.state;
225+
226+
return (
227+
<li>
228+
{editMode ? (
229+
<input
230+
type="text"
231+
value={editText}
232+
onChange={this.onChangeEditText}
233+
/>
234+
) : (
235+
<span>
236+
<strong>{message.user.username}</strong> {message.text}{' '}
237+
{message.editedAt && <span>(Edited)</span>}
238+
</span>
239+
)}
240+
241+
{editMode ? (
242+
<span>
243+
<button onClick={this.onSaveEditText}>Save</button>
244+
<button onClick={this.onToggleEditMode}>Reset</button>
245+
</span>
246+
) : (
247+
<button onClick={this.onToggleEditMode}>Edit</button>
248+
)}
249+
250+
{!editMode && (
251+
<button
252+
type="button"
253+
onClick={() => onRemoveMessage(message.uid)}
254+
>
255+
Delete
256+
</button>
257+
)}
258+
</li>
259+
);
260+
}
261+
}
262+
263+
const Messages = withFirebase(MessagesBase);
264+
13265
const condition = authUser => !!authUser;
14266

15267
export default compose(
268+
withFirebase,
16269
withEmailVerification,
17270
withAuthorization(condition),
18271
)(HomePage);

0 commit comments

Comments
 (0)