ReactとFirebaseを使ってチャットアプリにアイコン画像を追加する方法
はじめに
名前とメッセージのみ表示されるチャットアプリに、アイコン画像を追加したいと思ったところ、意外と躓いたので私なりの方法を紹介します。
手順は以下の通りです。
- <input type="file" />を使いフォームで画像を送信する
- FirebaseのStorageで画像を参照、アップロードする
- Storageからダウンロードし、Authenticationのユーザー情報を更新する
- Firestoreのコレクションにユーザー情報の画像を追加する
- Firestoreからデータを取得する
- データを画面上にレンダリングする
前提:追加前のコード
アイコン画像追加前の処理は以下のようになっています。
SignUp.js
にてサインアップフォームにて入力したメールアドレスとパスワードを元に、
firebase.auth().createUserWithEmailAndPassword(email, password)
Authenticationにユーザーを作成。
その処理が成功後、
user.updateProfile({displayName: name});
ユーザー名を更新し、history.push("/");
でチャットルームへ遷移。
AuthService.js
でAuthenticationからユーザー情報を取得。
useEffect(() => {
firebase.auth().onAuthStateChanged(function (user) {
setUser(user);
});
}, []);
Room.js
にて
firebase.firestore().collection("messages").add({
content: value,
user: user.displayName,
time: firebase.firestore.FieldValue.serverTimestamp(),
});
Firestoreのメッセージコレションに、
- フォームで入力したチャットメッセージの内容
- ユーザー名
- 投稿時間
を追加。
firebase
.firestore()
.collection("messages")
.orderBy("time")
.onSnapshot((snapshot) => {
const messages = snapshot.docs.map((doc) => {
return doc.data();
});
setMessages(messages);
変数message
にfirestoreのメッセージコレクションの内容を配列として生成し、リアルタイムアップデート。
なおその際に、orderBy("time")
を用いて、前述の変数time
の順番にソートしています。
map関数で<ul></ul>
内にリストとして各コンポーネントにmessage
を画面上にレンダリング。
以上です。
以下は全体のコードになります。
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { Login } from "./pages/Login";
import { SignUp } from "./pages/SignUp";
import { Room } from "./pages/Room";
import { AuthProvider } from "./AuthServise";
import { LoggedInRoute } from "./LoggedInRoute";
export const App = () => {
return (
<AuthProvider>
<BrowserRouter>
<Switch>
<LoggedInRoute exact path="/" component={Room} />
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={SignUp} />
</Switch>
</BrowserRouter>
</AuthProvider>
);
};
import React, { useState } from "react";
import firebase from "../config/firebase";
export const SignUp = ({ history }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
user.updateProfile({
displayName: name,
});
history.push("/");
})
.catch((err) => {
console.log(err);
});
};
return (
<>
<h1>Sign Up</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">E-mail</label>
<input
type="email"
name="email"
id="email"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
value={password}
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<label htmlFor="name">Name</label>
<input
type="name"
name="name"
id="name"
value={name}
placeholder="name"
onChange={(e) => setName(e.target.value)}
/>
</div>
<button type="submit">Sign Up</button>
</form>
</>
);
};
import React, { useEffect, useState } from "react";
import firebase from "./config/firebase";
const AuthContext = React.createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
firebase.auth().onAuthStateChanged(function (user) {
setUser(user);
});
}, []);
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
};
export { AuthContext, AuthProvider };
import React, { useContext, useEffect, useState } from "react";
import { AuthContext } from "../AuthServise";
import firebase from "../config/firebase";
import { Item } from "./Item";
export const Room = () => {
const [value, setValue] = useState("");
const [messages, setMessages] = useState(null);
useEffect(() => {
firebase
.firestore()
.collection("messages")
.orderBy("time")
.onSnapshot((snapshot) => {
const messages = snapshot.docs.map((doc) => {
return doc.data();
});
setMessages(messages);
});
}, []);
const user = useContext(AuthContext);
const handleSubmit = (e) => {
e.preventDefault();
firebase.firestore().collection("messages").add({
content: value,
user: user.displayName,
time: firebase.firestore.FieldValue.serverTimestamp(),
avatar: user.photoURL,
});
setValue("");
};
return (
<>
<h1>Room</h1>
<ul>
{messages &&
messages.map((message, index) => {
return (
<Item
key={index}
user={message.user}
content={message.content}
/>
);
})}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">送信</button>
</form>
<button onClick={() => firebase.auth().signOut()}>Logout</button>
</>
);
};
export const Item = ({ user, content }) => {
return (
<li>
{user} : {content}
</li>
);
};
チャットルームの表示は以下になっています。
アイコン画像追加処理
本題のアイコン画像追加の処理を行っていきます。
<input type="file" />を使いフォームで画像を送信する
SignUp.js
に画像送信用のinput
を追加します。
const [avatar, setAvatar] = useState(null);
...
<div>
<label htmlFor="avatar">ユーザー画像</label>
<input
type="file"
name="avatar"
id="avatar"
onChange={(e) => setAvatar(e.target.files[0])}
/>
</div>
Email,Password,Nameではvalue属性を用いてStateを更新していましたが、こちらはファイルへのパスを表す文字列となってしまうため、今回はfiles属性を使います。
files属性では選択されたfilelist
が配列として入っています。
今回はファイルを1つだけ選択する仕様にしているため、インデックス番号[0]のファイルをavatarのState更新に使用しています。
FirebaseのStorageで画像を参照、アップロードする
Authenticationには画像を直接追加することは出来ないため、storageにアップロードし、そのURLをプロフィールに追加します。
まずstorageへのアップロードからです。
const iconRef = firebase
.storage()
.ref()
.child("user-image/" + avatar.name);
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
iconRef.put(avatar).then(() => {
iconRef.getDownloadURL().then((url) => {
user.updateProfile({
displayName: name,
photoURL: url,
});
history.push("/");
});
});
})
.catch((err) => {
console.log(err);
});
行った処理を説明します。
firebase.storage().ref().child("user-image/" + avatar.name);
storageのuser-image
フォルダにavatar
画像へのパスを参照します。
createUserWithEmailAndPassword(email, password)
の処理が成功したら、putメソッド
でstorageにアップロードします。
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
iconRef.put(avatar).then(() => {
Storageからダウンロードし、Authenticationのユーザー情報を更新する
アップロードが成功したら、getDownloadURL()
で、そのダウンロードURLを取得する処理を行います。
iconRef.put(avatar).then(() => {
iconRef.getDownloadURL().then((url) => {
URLを取得したら、Authenticationのユーザープロフィール内photoURL
(プロフィール写真のURL)に取得したURLを追加します。
iconRef.getDownloadURL().then((url) => {
user.updateProfile({
displayName: name,
photoURL: url,
});
これでユーザープロフィールに画像が更新されました。
Firestoreのコレクションにユーザー情報の画像を追加する
チャットルームで画像を表示するため、Firestoreのメッセージコレクションにプロフィール写真を追加します。
Room.js
のhandleSubmit
内、メッセージコレクションへの追加要素にavatar: user.photoURL
を加えます。
このuser
はAuthService.js
内でAuthenticationから取得したユーザー情報です。
const handleSubmit = (e) => {
e.preventDefault();
firebase.firestore().collection("messages").add({
content: value,
user: user.displayName,
time: firebase.firestore.FieldValue.serverTimestamp(),
avatar: user.photoURL,
});
setValue("");
};
Firestoreからデータを取得する
先程のメッセージコレクションからデータを取得します。
この処理に画像追加処理前との変更点はありません。
firebase
.firestore()
.collection("messages")
.orderBy("time")
.onSnapshot((snapshot) => {
const messages = snapshot.docs.map((doc) => {
return doc.data();
});
setMessages(messages);
データを画面上にレンダリングする
Room.js
の<ul></ul>
内でItemコンポーネントにメッセージコレクションのavatar
をオブジェクトとして渡し、
Itemコンポーネントの<li></li>
内に
<img style={{ width: "100px" }} src={avatar} alt="" />
を追加。
これでアイコン画像をチャットルームに表示することが出来ました。
変更後のコード
変更後はこのようなコードになります。
import React, { useState } from "react";
import firebase from "../config/firebase";
export const SignUp = ({ history }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [avatar, setAvatar] = useState(null);
const handleSubmit = (e) => {
e.preventDefault();
const iconRef = firebase
.storage()
.ref()
.child("user-image/" + avatar.name);
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(({ user }) => {
iconRef.put(avatar).then(() => {
iconRef.getDownloadURL().then((url) => {
user.updateProfile({
displayName: name,
photoURL: url,
});
history.push("/");
});
});
})
.catch((err) => {
console.log(err);
});
};
return (
<>
<h1>Sign Up</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">E-mail</label>
<input
type="email"
name="email"
id="email"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
value={password}
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<label htmlFor="name">Name</label>
<input
type="name"
name="name"
id="name"
value={name}
placeholder="name"
onChange={(e) => setName(e.target.value)}
/>
</div>
<div>
<label htmlFor="avatar">ユーザー画像</label>
<input
type="file"
name="avatar"
id="avatar"
onChange={(e) => setAvatar(e.target.files[0])}
/>
</div>
<button type="submit">Sign Up</button>
</form>
</>
);
};
import React, { useContext, useEffect, useState } from "react";
import { AuthContext } from "../AuthServise";
import firebase from "../config/firebase";
import { Item } from "./Item";
export const Room = () => {
const [value, setValue] = useState("");
const [messages, setMessages] = useState(null);
useEffect(() => {
firebase
.firestore()
.collection("messages")
.orderBy("time")
.onSnapshot((snapshot) => {
const messages = snapshot.docs.map((doc) => {
return doc.data();
});
setMessages(messages);
});
}, []);
const user = useContext(AuthContext);
const handleSubmit = (e) => {
e.preventDefault();
firebase.firestore().collection("messages").add({
content: value,
user: user.displayName,
time: firebase.firestore.FieldValue.serverTimestamp(),
avatar: user.photoURL,
});
setValue("");
};
return (
<>
<h1>Room</h1>
<ul>
{messages &&
messages.map((message, index) => {
return (
<Item
key={index}
user={message.user}
content={message.content}
avatar={message.avatar}
/>
);
})}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">送信</button>
</form>
<button onClick={() => firebase.auth().signOut()}>Logout</button>
</>
);
};
export const Item = ({ user, content, avatar }) => {
return (
<li>
<img style={{ width: "100px" }} src={avatar} alt="" />
<br />
{user} : {content}
</li>
);
};
Discussion