はじめに
よく使いがちなコンポーネントをまとめてみました。
Material Uiを使用してます。
Loading....
型宣言(LoadingType.tsx)
import { Dispatch, SetStateAction } from "react";
export type LoadingStatus = {
open: boolean;
type: string;
message: string;
};
export type LoadingContextType = {
progress: boolean;
setProgress: Dispatch<SetStateAction<boolean>>;
handleClose: (event: any, reason: string) => void;
status: LoadingStatus;
setStatus: Dispatch<SetStateAction<LoadingStatus>>;
};
ロジック(useLoading.tsx)
import { createContext, useContext, useState } from "react";
import { LoadingContextType, LoadingStatus } from "types/LoadingType";
const LoadingContext = createContext<LoadingContextType | null>(null);
export const LoadingProvider = ({ children }: any) => {
const [progress, setProgress] = useState(false);
const [status, setStatus] = useState<LoadingStatus>({
open: false,
type: "success",
message: "成功しました。",
});
const handleClose = (event: any, reason: string) => {
if (reason === "clickaway") {
return;
}
setStatus({ ...status, open: false });
};
const value = { progress, setProgress, handleClose, status, setStatus };
return (
<LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
);
};
export const useLoading = () => {
return useContext(LoadingContext);
};
コンポーネント(ELoading.tsx)
import Snackbar from "@mui/material/Snackbar";
import MuiAlert from "@mui/material//Alert";
import Backdrop from "@mui/material/Backdrop";
import CircularProgress from "@mui/material/CircularProgress";
import { useLoading } from "hooks/useLoading";
const ELoading = () => {
const { progress, handleClose, status } = useLoading();
return (
<>
<Backdrop open={progress}>
<CircularProgress />
</Backdrop>
<Snackbar
open={status.open}
autoHideDuration={3000}
onClose={handleClose}
>
<MuiAlert
onClose={handleClose}
severity={status.type}
elevation={6}
variant="filled"
>
{status.message}
</MuiAlert>
</Snackbar>
</>
);
};
export default ELoading;
使用例
app.tsx
import type { AppProps } from "next/app";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider } from "@mui/material/styles";
import { LoadingProvider } from "hooks/useLoading";
import ELoading from "@/components/elements/Loading/ELoading";
import theme from "../theme";
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<ThemeProvider theme={theme}>
<LoadingProvider>
<CssBaseline />
<Component {...pageProps} />
<ELoading />
</LoadingProvider>
</ThemeProvider>
</>
);
}
API通信の例(Firebaseの認証。新規登録API)
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
import { firebaseApp } from "@/lib/FirebaseConfig";
import { useLoading } from "hooks/useLoading";
export const AuthRegisterAPI = () => {
const { setStatus, setProgress, } = useLoading();
const doRegister = (email: string, password: string) => {
const app = firebaseApp;
const auth = getAuth(app);
setProgress(true); //ローディングスタート
createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
setStatus({
open: true,
type: "success",
message: "登録に成功しました。",
});
})
.catch((error) => {
if (error) {
setStatus({ //エラーメッセージ表示
open: true,
type: "error",
message: "登録に失敗しました。。",
});
}
})
.then(() => {
setProgress(false); //ローディング終了
});
};
return doRegister;
};
入力テキストフィールド
import TextField from "@mui/material/TextField";
import { FC, ChangeEvent } from "react";
import { Interpolation, Theme } from "@emotion/react";
interface Props {
value?: string; // 値
inputRef?: any; // 状態管理
label?: string; // タイトル
error?: boolean; // エラー表現
helperText?: string; // エラーテキスト表示
color?: "primary" | "secondary" | "error" | "info" | "success" | "warning";
variant?: "filled" | "outlined" | "standard";
size?: "medium" | "small";
fullWidth?: boolean;
css?: Interpolation<Theme>; // Emotion (CSS in JS)
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
}
const ETextField: FC<Props> = (props) => {
const {
value,
inputRef,
error,
helperText,
label,
color,
variant = "outlined",
size = "medium",
fullWidth = true,
css,
onChange,
} = props;
return (
<>
<TextField
label={label}
color={color}
variant={variant}
value={value}
ref={inputRef}
error={error}
helperText={helperText}
size={size}
fullWidth={fullWidth}
css={css}
onChange={onChange}
/>
</>
);
};
export default ETextField;
使用例(react-hook-form使用)
import { Controller } from "react-hook-form";
<Controller
name="password"
control={control}
rules={{
required: "パスワードは必須項目です。",
minLength: {
value: 8,
message: "パスワードは最低8文字必要です。",
},
}}
render={({ field }) => (
<ETextField
label="Password"
onChange={field.onChange}
error={!!errors.password}
helperText={errors.password?.message}
/>
)}
/>
ボタン
import Button from "@mui/material/Button";
import { FC, MouseEventHandler } from "react";
interface Props {
label?: string;
type?: "button" | "submit" | "reset";
href?: string;
size?: "small" | "medium" | "large";
color?:
| "success"
| "inherit"
| "primary"
| "secondary"
| "error"
| "info"
| "warning";
onClick?: MouseEventHandler<HTMLButtonElement>;
}
const EButton: FC<Props> = (props) => {
const { label, type, href, size, color = "success", onClick } = props;
return (
<>
<Button
variant="contained"
type={type}
href={href}
size={size}
color={color}
onClick={onClick}
>
{label}
</Button>
</>
);
};
export default EButton;
使用例
<EButton label="SIGN UP" type="submit" />
終わりに
私が以前の案件に関わっていた際、ロジックとコンポーネントを分けずにそのまま書いてしまった結果、とても長く見にくいコードになってしまった反省も踏まえ、型やロジックを別ファイルに分けimportし使用するようにしています。
また、デザインをある程度統一するためにMaterial UIのコンポーネントをそのまま使うのではなく、ETextFieldなどにして共通コンポーネント化としてます。
私の短いエンジニア経歴から導き出した「これなら使いやすいんじゃないか」を考えながら作成しておりますので、使いにくいわ!管理しにくいわ!などありましたらぜひぜひご意見お待ちしております。