1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

Loading....

画面収録 2023-11-23 10.50.13.gif

型宣言(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;
};

入力テキストフィールド

画面収録 2023-11-23 11.43.15.gif

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}
    />
  )}
/>

ボタン

画面収録 2023-11-23 11.41.45.gif

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などにして共通コンポーネント化としてます。

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?