LoginSignup
8
8

More than 1 year has passed since last update.

【React】よく使うhooks・Component集

Last updated at Posted at 2021-12-07

おはこんばんちは、@ちーずです。
アドベントカレンダーもやっとこさ1週間ですね!!

本日のテーマは、「よく使うhooks・Component集」です!!
ライブラリ的なものをひたすら紹介するだけの記事です。

hooks

react-use

便利なhooks寄せ集め、みたいなもの。

チュートリアルやコードの例で見るuseCounter
最初の呼び出しを無視するuseEffectが実装されているuseUpdateEffectなど、
自分でかくとちょっとめんどくさいような、かゆいところに手が届くhooksがたくさん入っています。

react-hook-form

ありとあらゆるフォーム周りのStateを取り使えるhooks。

フォームって自前で実装すると、結構大変ですよね。
react-hook-formでは、バリデーションエラーやエラー処理はもちろんのこと、
入力値を検知して出し分けしたり、変更時に処理を走らせたりなど、結構柔軟に表現することができます。

ただ、個人的に、ちょっと難しいです。。

Component

基本的にUIフレームワークを使わない前提で紹介します!

Selector - React Select

通常のセレクターだけではなく、
複数選択できるセレクターや、タグ表示にできるセレクターなど、
さまざまなパターンのセレクターが表現できます。

CSSも結構自由自在に書くことができるので、おすすめです。

Modal - react-modal

Reactのコミュニティが提供してくれているモーダル。
必要最低限且つシンプルな機能が兼ね備えられているため、中央に表示するモーダル
スタイルもオブジェクトスタイルで拡張可能。
(中央以外のレイアウトはちょっとめんどくさかった)

Tab - react-tabs

Reactのコミュニティが提供してくれているタブ。
モーダル同様、必要最低限且つシンプルな機能が兼ね備えられている。
ちゃんとroleの指定もできる

Transition - react-transition-group

またまたReactのコミュニティが提供してくれているアニメーションを簡単に実装できるComponent。

Accordion

正直あまり良いプラグインはなかったので自前で実装しました。
Stateによってheaderのスタイルが変わる(+-みたいな)ことを実現したかったため、
headerとbodyに開閉状態を配布するプロバイダーで囲っています。
headerやbodyは上から自由にスタイルを拡張できるよう作りました。

サンプルコード
AccordionProvider.tsx
import { useReducer, createContext, DispatchWithoutAction, FC } from 'react';

export type AccordionContextType = {
  isOpen: boolean;
  toggleAccordion: DispatchWithoutAction;
  accordionName: string;
};

export const AccordionContext = createContext<AccordionContextType>({
  isOpen: false,
  toggleAccordion: () => {
    // no-op
  },
  accordionName: ''
});

export type AccordionProviderProps = {
  initialIsOpen: boolean;
};

export const AccordionProvider: FC<AccordionProviderProps> = ({
  children,
  initialIsOpen,
  accordionName
}) => {
  const [isOpen, toggleAccordion] = useReducer(
    (state) => !state,
    initialIsOpen
  );

  return (
    <AccordionContext.Provider value={{ isOpen, toggleAccordion, accordionName }}>
      {children}
    </AccordionContext.Provider>
  );
};

export const useAccordionContext = (): AccordionContextType => {
  return useContext<AccordionContextType>(AccordionContext);
};

AccordionHeader.tsx
import React, { VFC, ReactNode } from 'react';
import { useAccordionContext } from 'AccordionProvider';

type Props = {
  children: ReactNode;
} & JSX.IntrinsicElements['div'];

export const AccordionHeader: VFC<Props> = ({
  children,
  ...attrs
}) => {
  const { isOpen, toggleAccordion, accordionName } = useAccordionContext();

  return (
    <div
      onClick={toggleAccordion}
      aria-controls={accordionName}
      aria-expanded={isOpen}
      {...attrs}
    >
      {children}
    </div>
  );
};
AccordionBody.tsx
import React, { FC, ReactNode } from 'react';
import { useAccordionContext } from 'AccordionProvider';
import { CSSTransition } from 'react-transition-group';

type Props = {
  children: ReactNode;
} & JSX.IntrinsicElements['div'];

export const AccordionHeader: FC<Props> = ({
  children,
  ...attrs
}) => {
  const { isOpen, accordionName } = useAccordionContext();
  // アニメーション周り色々している
  const { bodyElm, transitionMethod } = useAccordionBody();

  return (
    <CSSTransition in={isOpen} timeout={duration} {...transitionMethod}>
      <div
        id={accordionName}
        aria-hidden={!isOpen}
        ref={bodyElm}
        css={styles.container(duration)}
        {...attr}
      >
        {children}
      </div>
    </CSSTransition>
  );
};

Rating Star

これもスター数が多いものがなく、心折れながらもなんとか実装しました。
なかなか強引な方法ですが、svgのグラデーションを用いて実現しています。
(拡張性はあまりないです。)

サンプルコード
RatingStars.tsx
import React, { FC } from 'react';
import { RatingStarIcon } from './RatingStarIcon';
import { css } from '@emotion/react';


type Props = {
  size: number;
  score: number;
  id: string;
  activeColor: string;
  disabledColor: string;
  gap?: SizeValues;
  max?: number;
} & JSX.IntrinsicElements['div'];

const styles = {
  container: (gap: number) => css`
    display: flex;

    > * {
      &:not(:last-child) {
        margin-right: ${gap / 10}rem;
      }
    }
  `,
};

export const RatingStars: FC<Props> = ({
  size,
  score,
  gap = '2px',
  id,
  activeColor.,
  disabledColor,
  max = 5,
  ...attrs
}) => {
  const starRate = (count: number, score: number): number => {
    if (score - count > 1) return 100;
    if (score - count > 0) return (score - count) * 100;
    return 0;
  };

  return (
    <div css={styles.container(gap)} {...attrs}>
      {[...Array(max)].map((_, index) => {
        const rate = starRate(index, score);
        return (
          <React.Fragment key={`star-${index}`}>
            <RatingStarIcon
              id={id}
              width={size}
              activeColor={activeColor}
              activeColor={disabledColor}
              height={size}
              rate={rate}
              index={index}
            />
          </React.Fragment>
        );
      })}
    </div>
  );
};
RatingStarIcon.tsx
import React, { FC } from 'react';

type Props = {
  id: string;
  rate: number;
  index: number;
  activeColor: string;
  disabledColor: string;
  width?: number;
  height?: number;
};

export const RatingStarIcon: FC<Props> = ({
  id,
  width = 30,
  height = 30,
  activeColor,
  disabledColor,
  index,
  rate,
}) => {
  return (
    <svg
      width={width}
      height={height}
      viewBox="0 0 30 30"
      xmlns="http://www.w3.org/2000/svg"
    >
      <defs>
        <linearGradient
          id={`gradient${index}_${id}`}
          x1="0"
          x2="1"
          y1="0"
          y2="0"
        >
          <stop offset="0%" stopColor={activeColor} />
          <stop offset={`${rate}%`} stopColor={activeColor} />
          <stop offset={`${rate}%`} stopColor={disabledColor} />
          <stop offset="100%" stopColor={disabledColor} />
        </linearGradient>
      </defs>
      <path
        fill={`url(#gradient${index}_${id})`}
        d="M15 24.1105L24.27 30L21.81 18.9L30 11.4316L19.215 10.4684L15 0L10.785 10.4684L0 11.4316L8.19 18.9L5.73 30L15 24.1105Z"
      />
    </svg>
  );
};

8
8
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
8
8