【Firebase】プロフィール編集画面を作成し、Firestore Storageにアバター画像を保存する
Firebase

【Firebase】プロフィール編集画面を作成し、Firestore Storageにアバター画像を保存する

作成日:2022年03月04日
更新日:2022年03月05日

前回は、Firestore Database を使い、React でメッセージ送信機能を実装しました。

firebase-react-firestore-create

【Firebase】Firestore Databaseを使い、Reactでメッセージ送信機能を実装する

今回は、プロフィール編集画面を作成し、Firestore Storage にアバター画像を保存します。

まずは、ヘッダーのプロフィールをクリックすると、プロフィール画面へ遷移するようにします。

MUI でプロフィール画面を作りましょう。

tsx
import React, { useState } from "react";
import {
Paper,
Typography,
Box,
TextField,
Button,
Container,
} from "@mui/material";
const Profile = () => {
const [name, setName] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.files);
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
};
return (
<Container maxWidth="sm">
<Paper sx={{ m: 4, p: 4 }}>
<Typography align="center">プロフィール編集</Typography>
<Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 4 }}>
<input type="file" accept="image/*" onChange={handleChange} />
<TextField
margin="normal"
required
fullWidth
id="name"
label="ユーザー名"
name="name"
autoComplete="name"
autoFocus
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
保存
</Button>
</Box>
</Paper>
</Container>
);
};
export default Profile;

image2

次に、react-router-domでプロフィール画面のパスを作成します。

App.tsx へ移動し、Routeを追加します。

tsx
<Route path="profile" element={<Profile />} />

Header.tsx へ移動し、リンクを設定しましょう。

tsx
<MenuItem onClick={handleClose}>
<Link href="profile" underline="none" color="inherit">
プロフィール
</Link>
</MenuItem>

プロフィール画面もヘッダーを表示させたいので、App.tsx でホーム画面とプロフィール画面のみヘッダーを表示するようにします。

react-router-domからOutletをインポートします。

tsx
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";

Outletchildrenみたいな役割を果たしてくれます。

Layout関数を作成します。

Layout関数の中にHeaderOutletを設定します。

tsx
function Layout() {
return (
<>
<Header />
<Outlet />
</>
);
}

App関数のRoutesの中にLayoutを設定します。

tsx
function App() {
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="profile" element={<Profile />} />
</Route>
<Route path="login" element={<Login />} />
<Route path="signup" element={<Signup />} />
<Route path="password-reset" element={<PasswordReset />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
);
}

プロフィール画面を確認すると、

image3

ヘッダーが表示されました。

ちなみに、ログイン画面では、

image4

ヘッダーが表示されていません。

このままでは、ホーム画面でヘッダーが二重で表示されるので、Home.tsx のHeaderは削除しておきましょう。

『ファイルを選択』をクリックすると、画像を選択できるようになっています。

image5

HTML のレイアウトではなく、MUI のボタンを作成し、全体を統一します。

まずは、inputタグの下にボタンを作成します。

tsx
<Button variant="contained" color="primary" component="span">
画像を選択
</Button>

inputタグにidを追加します。

また、Buttonを label タグで囲みます。

labelタグにhtmlForを設定し、inputタグのidを指定します。

inputタグをdisplay:noneで非表示にします。

tsx
<input
id="image"
type="file"
accept="image/*"
onChange={handleChange}
style={{ display: "none" }}
/>
<label htmlFor="image">
<Button variant="contained" color="primary" component="span">
画像を選択
</Button>
</label>

では、動作確認してみます。

image6

画像を追加し、Console を確認すると、

image7

画像のデータが表示されました。

このままでは、画像が選択されているか、画面では分からないので、画面に画像を表示させます。

useState で画像データの状態を管理しましょう。

tsx
const [image, setImage] = useState<File | null>();
tsx
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files !== null) {
setImage(e.target.files[0]);
}
};

次に、MUI のAvatarを使い、画像を表示する場所を作成します。

Avatarの src には、imageがある場合、URL.createObjectURLimageを指定します。

imageがない場合は、””としておきましょう。

tsx
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Avatar src={image ? URL.createObjectURL(image) : ""} alt="" />
<div>
<input
id="image"
type="file"
accept="image/*"
onChange={handleChange}
style={{ display: "none" }}
/>
<label htmlFor="image">
<Button variant="contained" color="primary" component="span">
画像を選択
</Button>
</label>
</div>
</Box>

image8

では、画像を選択してみます。

image9

Avatar が設定されました。

画像が選択できたので、次は、Firebase の Storage に画像が保存できるようにします。

Firebase の Storage にアクセスします。

Rules タグをクリックします。

今のところ、ファイルの read や write が禁止されています。

image10

こちらを、認証されている場合は許可するようにします。

rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}

公開をクリックします。

image11

Profile.tsx へ戻ります。

以前 Firebase の初期設定した、Firebase フォルダの firebaseConfig からfirebaseAppをインポートします。

Firebase の設定は、こちらをご覧ください。

firebase-project-config-setting

【Firebase】Firebase Project Configを設定する

firebase-firestore-database-gets

【Firebase】Firestore Databeseのデータを、フロントエンドに表示する

firebase-storage-get-preview

【Firebase】Storageで保存した画像をブラウザに表示する

firebaseAppfirestorageを使用します。

tsx
const firestorage = firebaseApp.firestorage;

handleSubmit 関数内で、try/catch を使います。

catch の場合は、エラーメッセージを表示するようにします。

tsx
const [error, setError] = useState(false);
tsx
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
} catch (err) {
console.log(err);
setError(true);
}
};
tsx
{
error && <Alert severity="error">送信できませんでした</Alert>;
}

firebase/storageからrefをインポートします。

tsx
import { ref } from "firebase/storage";

ref の第一引数に、先程設定したfirestorage、第二引数にファイル名としてimageオブジェクトのnameを指定します。

tsx
try {
if (image) {
const imageRef = ref(firestorage, image.name);
}
} catch (err) {
console.log(err);
setError(true);
}

firebase/storageからuploadBytesをインポートします。

tsx
import { ref, uploadBytes } from "firebase/storage";

uploadBytesの第一引数に imageRef、第二引数に image を指定します。

thenConsoleに送信内容を表示するようにします。

tsx
try {
if (image) {
const imageRef = ref(firestorage, image.name);
uploadBytes(imageRef, image).then((snapshot) => {
console.log("Uploaded a file!", snapshot);
});
}
} catch (err) {
console.log(err);
setError(true);
}

では、動作確認してみます。

image12

『保存』をクリックすると、

image13

画像が送信されたようです、

Firebase の Storage を確認してみましょう。

image14

画像が保存されていました。

次回は、

firebase-database-react-user

【Firebase】プロフィール画面で作成したユーザー情報を、Firestore Databaseに保存する

© 2024あずきぱんウェブスタジオ