[React + MUI] 今どきの WEB サイト/スマホアプリによくある簡易フィードバックフォームをつくる

こんにちは、広野です。

最近の WEB サイトやスマホアプリを使っていて、ユーザになるべく手間をかけさせない簡易なフィードバックフォームがあるのを見かけませんか?

あれって、開発元がユーザがそのサイトやアプリをどう思っているのか知りたくて作っているんです。(当たり前か)
ユーザはあまり気にしないかもしれませんが、開発元は作ったサイトやアプリがユーザに受け入れられているのかめっちゃ気にしてまして、ユーザからのフィードバック以外にそれを知る術がありません。

しかし、フィードバックフォームの入力項目が多いと途端にユーザは書いてくれなくなるので、極力項目を削ぎ落したフォームにせざるを得ないのが実情です。もちろん、開発元はもっと根掘り葉掘り聞きたいんですけどね。

本記事では、そんな簡易フィードバックフォームを React と MUI で画面開発してみましたので、コードを紹介します。フィードバックデータを受け取るバックエンドは AWS で構築していますが、記事が長くなってしまうので省略します。

つくったもの

スクリーンショットを貼り付けます。

あるユーザ操作の終了後、画面①の右下に、「フィードバックを書く」というさりげないボタンがあります。(邪魔にならないように)
ボタンを押すと、簡易フィードバック用のダイアログが画面にオーバーレイする形で表示されます。

画面③、「クイズはいかがでしたか?」ダイアログの評価レーティングが星の数で表されるので、ユーザが評価した位置の星を押してもらいます。メッセージは強制ではないですが、何か書いてくれたら嬉しいので用意しています。
送信ボタンを押すと、画面④に遷移し、元々そこにいたアバターがフィードバックのお礼を言います。

ちなみにこの画面にはアバターを表示しないオプションがあり、その場合はアバターにお礼を言わせることができないため、Snackbar と言われる小さいメッセージ欄を画面下部に表示させます。これは 6 秒後に自動で消えるようにしています。

使用した MUI パーツ

この画面を実現するにあたり、多くの MUI のパーツを使っています。
MUI とは、マテリアルデザインに基づいた WEB 画面を開発するための部品を提供してくれる、React 用モジュールです。

MUI: The React component library you always wanted
MUI provides a simple, customizable, and accessible library of React components. Follow your own design system, or start...

以下に、使用した MUI パーツを列挙します。

画面パーツ

  • Floating action button
    「フィードバックを書く」ボタンです。
React Floating Action Button (FAB) component - Material UI
A Floating Action Button (FAB) performs the primary, or most common, action on a screen.
  • Dialog
    「クイズはいかがでしたか?」ダイアログ画面の枠や、その表示コントロールに使っています。
React Dialog component - Material UI
Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks.
  • Rating
    星の数で表す、ユーザ評価入力欄です。
React Rating component - Material UI
Ratings provide insight regarding others' opinions and experiences, and can allow the user to submit a rating of their o...
  • TextField
    メッセージ入力欄です。
React Text Field component - Material UI
Text Fields let users enter and edit text.
  • Button
    「閉じる」や「送信」ボタンです。
React Button component - Material UI
Buttons allow users to take actions, and make choices, with a single tap.
  • Icons
    各ボタンには、その意味をイメージするためのアイコンを入れています。
React Icon Component - Material UI
Guidance and suggestions for using icons with Material UI.
  • Snackbar
    アバター非表示時のお礼メッセージ表示用です。
React Snackbar component - Material UI
Snackbars (also known as toasts) are used for brief notifications of processes that have been or will be performed.
  • Alert
    アバター非表示時のお礼メッセージ表示用です。本記事では Snackbar と組み合わせて使用します。
React Alert component - Material UI
Alerts display brief messages for the user without interrupting their use of the app.

レイアウト調整

  • Box
    画面に任意のサイズ、位置の表示枠をつくるためのレイアウト調整用パーツです。
    「フィードバックを書く」ボタンを画面右下に表示させるために使用しています。
React Box - Material UI
The Box component is a generic, theme-aware container with access to CSS utilities from MUI System.
  • Stack
    画面内の1列、1行に表示させる画面パーツ(HTMLタグ単位)をどのように並べるか、コントロールするパーツです。超便利。
React Stack component - Material UI
Stack is a container component for arranging elements vertically or horizontally.

トランジション(アニメーション)

  • Zoom
    あるユーザ操作が終わってから 2 秒後に「フィードバックを書く」ボタンが「ズームイン」するアニメーションを入れています。後からアニメーション付きで表示させることで、ユーザにボタンがあることを気付かせたい意図があります。
React Transition component - Material UI
Transitions help to make a UI expressive and easy to use.

本記事では省略しますが、他にも Fade や Slide などのアニメーションもところどころで使用しています。例えば、アバターはスライドインして表示させる、吹き出しによるメッセージは少し遅らせて表示する、などです。

React コード

実際のコードは量が多いため、関係するところだけ抜粋します。
import するモジュールは省略します。MUI の公式ドキュメント通りです。
すみません、MUI ではない通常 HTML タグのCSS やステートの定義も省略させて頂きます。setXxx という関数は xxx という名前のステートを更新するものとご理解ください。

「フィードバックを書く」ボタン

//Dialogオープン
const handleOpenDialog = () => {
  setIsDialogOpen(true);
};

//中略

{/* Floating Action Button to show the feedback dialog */}
<Zoom in={isFeedbackFabOpen} mountOnEnter unmountOnExit style={{transitionDelay:"2000ms"}}>
  <Box role="presentation" sx={{position:"fixed",bottom:16,right:16}}>
    <Fab onClick={handleOpenDialog} variant="extended" size="small" color="info" sx={{fontFamily:"inherit",my:3}} aria-label="show-feedback">
      <RateReviewIcon />フィードバックを書く
    </Fab>
  </Box>
</Zoom>
  • あるユーザ操作によりこの画面が表示完了になったタイミングでステート isFeedbackFabOpen が true になります。
    そうすると 2 秒待ってから Zoom が発動し、Fab、つまり「フィードバックを書く」ボタンがズームインします。
  • Box により、「フィードバックを書く」ボタンの表示位置を「下から 16 px、右から 16 px」に指定しています。Fab は、デフォルトで既存画面にオーバーレイして表示されます。
  • 「フィードバックを書く」という文字列の左には、RateReviewIcon というアイコンを表示させています。
  • Fab には onClick で handleOpenDialog という関数を呼び出すようにしています。ボタンを押すと、ダイアログを表示させる関数です。単純に、ダイアログの表示をコントロールするフラグにしているステート isDialogOpen を true にするだけです。

「クイズはいかがでしたか?」ダイアログ

//Dialog Styles
const textfieldStyles = {
  fontFamily: "inherit",
  backgroundColor: "white"
};
const textlabelStyles = {
  fontFamily: "inherit"
};

//Dialogクローズ
const handleCloseDialog = () => {
  setIsDialogOpen(false);
};

//フィードバック送信関数
const putFeedbackQuiz = async (id) => {
  //ダイアログオープン
  setIsDialogOpen(false);
  //Fabクローズ
  setIsFeedbackFabOpen(false);
  //スナックバーで御礼 (if Avator disabled)
  setIsSnackbarOpen(true);
  //アバターからフィードバックを御礼 (if Avator enabled)
  setAvatorMessage(false);
  setAvatorFeedbackReply("フィードバックありがとね!サービス改善がんばるからねー!");
  setAvatorEmotion("glad"); //アバターの表情を笑顔に変える
  //フィードバック記録
  putFeedback(id, username, feedbackMessageQuiz, feedbackRatingQuiz, headerdata);
};

//中略

{/* Feedback form */}
<Dialog open={isDialogOpen} onClose={handleCloseDialog} hideBackdrop={true} keepMounted>
  <DialogTitle sx={{fontFamily:"inherit"}}>クイズはいかがでしたか?</DialogTitle>
  <DialogContent>
    <Stack direction="column" justifyContent="flex-start" alignItems="center" spacing={1.5}>
      <Stack direction="row" justifyContent="center" alignItems="center" spacing={1.5}>
        <span className="smalltxt50">Bad</span>
        <Rating name="rate-quiz" value={feedbackRatingQuiz} icon={<StarRoundedIcon />} emptyIcon={<StarOutlineRoundedIcon />} onChange={(event, newValue) => {setFeedbackRatingQuiz(newValue)}} precision={1} />
        <span className="smalltxt50">Good</span>
      </Stack>
      <TextField name="message-quiz" value={feedbackMessageQuiz} onChange={(e) => {setFeedbackMessageQuiz(e.target.value)}} id="message-quiz" label="メッセージ" fullWidth multiline minRows={3} maxRows={3} variant="outlined" margin="dense" size="small" sx={{my:2}} InputProps={{style:textfieldStyles}} InputLabelProps={{style:textlabelStyles}} />
    </Stack>
  </DialogContent>
  <DialogActions>
    <Button onClick={handleCloseDialog} variant="text" size="small" startIcon={<RemoveCircleRoundedIcon />} sx={{mr:3}}>閉じる</Button>
    <Button id={"quiz-" + quizgroup} onClick={(e) => {putFeedbackQuiz(e.target.id)}} disabled={(feedbackRatingQuiz) ? false : true} variant="contained" size="small" endIcon={<SendIcon />}>送信</Button>
  </DialogActions>
</Dialog>
  • 前述「フィードバックを書く」ボタンを押すことにより、ダイアログを表示するフラグ isDialogOpen が true になることでダイアログが表示されます。
  • ダイアログの中は DialogTitle、DialogContent、DialogActions というパーツに分かれており、それぞれタイトル、コンテンツ、アクションを定義していきます。
  • コンテンツには、入力欄を表示させています。Rating で星を、TextFieldでメッセージ入力欄を表示させます。
  • 「閉じる」ボタンを押すと handleCloseDialog 関数を呼び出し、ダイアログを表示するフラグ isDialogOpen を false にします。
  • 「送信」ボタンを押すと putFeedbackQuiz 関数を呼び出し、いくつかのフラグを変更します。(コード内参照)
    putFeedback 関数を呼び出し、送りたい情報を渡します。putFeedback 関数は他の画面からも使用するため別コンポーネントでアプリ内共通関数として定義しており、単に axios でフィードバックデータを受け取るためのバックエンドにデータを送るだけのものとなっています。

「フィードバックありがとう!」メッセージ

アバターを非表示にするオプションを選択した場合、画面④’ のように画面下部に緑色の「フィードバックありがとう!」メッセージ (Snackbar) を表示しています。

import MuiAlert from '@mui/material/Alert';

//Alert Config
const Alert = React.forwardRef(function Alert(props, ref) {
  return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});

//中略

{/* Thank you message for sending feedback if Avator is disabled */}
<Snackbar open={isSnackbarOpen} autoHideDuration={6000} onClose={handleCloseSnackbar} anchorOrigin={{vertical:"bottom",horizontal:"center"}}>
  <Alert onClose={handleCloseSnackbar} severity="success" sx={{width:"100%"}}>フィードバックありがとう!</Alert>
</Snackbar>
  • 「送信」ボタンを押したときに Snackbar を表示するフラグ isSnackbarOpen を true にします。
  • Snackbar で Alert を囲むことで、Alert のデザインを Snackbar で使用しています。
  • Snackbar は 6 秒後に自動的に表示が消えますが、クリックしても消えるように Alert の onClose で Snackbar を表示するフラグ isSnackbarOpen を false に変更するようにしています。

まとめ

いかがでしたでしょうか?

アプリのロジック的には大したことないのですが、見た目の微調整に苦労しました。MUI パーツのオプションパラメータは、試行錯誤の上設定したものになっています。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもAWSすげー!って気持ちの方が強いです。
取得資格:AWS 認定は12資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2023 Japan AWS Top Engineer 受賞
2022 - 2023 Japan AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする
クラウドに強いによるエンジニアブログです。
SCSKは専門性と豊富な実績を活かしたクラウドサービス USiZE(ユーサイズ)を提供しています。
USiZEサービスサイトでは、お客様のDX推進をワンストップで支援するサービスの詳細や導入事例を紹介しています。
アプリケーション開発ソリューション
シェアする
タイトルとURLをコピーしました