使いがちなコンポーネント

# はじめに


よく使いがちなコンポーネントをまとめてみました。
Material Uiを使用してます。


# Loading....
![画面収録 2023-11-23 10.50.13.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625517/bbd4ec2e-86b3-7b1b-c8e6-39cbd56fc723.gif)
### 型宣言(LoadingType.tsx)
```react
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)
```react
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）
```react
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
```react
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)
```react
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;
};

```
# 入力テキストフィールド

![画面収録 2023-11-23 11.43.15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625517/cab6d8ff-8c24-541d-73e8-3ae7eafe8d19.gif)

```react
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使用)
```react
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}
    />
  )}
/>
```

# ボタン

![画面収録 2023-11-23 11.41.45.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3625517/385c3287-bce0-ca53-5508-2917f789f62d.gif)

```react
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;

```

## 使用例
```react
<EButton label="SIGN UP" type="submit" />
```

# 終わりに
私が以前の案件に関わっていた際、ロジックとコンポーネントを分けずにそのまま書いてしまった結果、とても長く見にくいコードになってしまった反省も踏まえ、型やロジックを別ファイルに分けimportし使用するようにしています。

また、デザインをある程度統一するためにMaterial UIのコンポーネントをそのまま使うのではなく、ETextFieldなどにして共通コンポーネント化としてます。

私の短いエンジニア経歴から導き出した「これなら使いやすいんじゃないか」を考えながら作成しておりますので、使いにくいわ！管理しにくいわ！などありましたらぜひぜひご意見お待ちしております。