🦔

MantineがUIライブラリの推しになった話

2024/08/06に公開

こんにちは!普段フロントエンド開発をしている人です🙌
(フロントエンド開発といってもレイアウトのデザインから実装までくらいの小さい範囲だけですが…)

今までCSS,SCSSをそれぞれちょっとお勉強して、Tailwind CSS, Material UI(MUI)そしてMantineとUIライブラリを使用してUI作成をしてきました。

Mantineを使用してみて自分が一番使いやすいなと感じたので、この記事はMantineをゴリ押しする内容(+ちょこっとこうなって欲しいという願望あり)になっています。

MantineのDemoと同じ内容のコードをそれぞれ書いていますが、詳しく知りたい方はリンクから飛んで見てみてください。


そもそもMantineとは

MantineはReactで使用できるコンポーネントライブラリです。
Mantineの特徴の一つとして、コンポーネントだけでなく、カスタムhooksライブラリformライブラリなども提供されています。

https://mantine.dev

Mantineの初期設定
https://mantine.dev/getting-started/

オススメその1 ~AppShell~

Mantineのコンポーネント群(Mantine Core)の中ににはAppShellというものがあります。
これは簡単にヘッダーやサイドバー、フッターなどを作成できるコンポーネントです。

作り方はこちら(コード内容はMantineのAppShellのUsage)

import { AppShell, Burger } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [opened, { toggle }] = useDisclosure();//ハンバーガーメニュー用toggle

  return (
    <AppShell
      header={{ height: 60 }} //ヘッダーのプロパティ(高さは必須項目)
      navbar={{
        width: 300,
        breakpoint: 'sm',
        collapsed: { mobile: !opened },
      }}//navbarのプロパティ
      padding="md"//mainコンテンツのpadding
    >
      <AppShell.Header>
        <Burger
          opened={opened}
          onClick={toggle}
          hiddenFrom="sm"
          size="sm"
        />
        <div>Logo</div>
      </AppShell.Header>

      <AppShell.Navbar p="md">Navbar</AppShell.Navbar>

      <AppShell.Main>Main</AppShell.Main>
    </AppShell>
  );
}

mainに関するstylesを追加しないと思った挙動にならないのが残念(私の書き方が悪いだけで、書かなくてもいい方法があるのかも)ですが、プロパティなどの情報が一気に見れるところや簡単に枠組みができるというメリットがでかすぎます。

このコンポーネント他のライブラリにも欲しい…
https://mantine.dev/core/app-shell/

オススメその2 ~多数のカスタムhooks~

上にも書きましたが、Mantine hooksというカスタムフックがまとまったライブラリがあります。
ここで一覧ずらっと書くのは見にくい(+書ける気力がない)ので、私がよく使うものや見ていて便利そうと思ったものだけ抜粋して紹介します。

use-mouse

refで指定された箱(divやBoxなど…)の中でマウスがどの位置にあるかを取得します。

作り方はこちら(コード内容はMantineのuse-mouseのUsage)
import { Text, Code, Group, Box } from '@mantine/core';
import { useMouse } from '@mantine/hooks';

function Demo() {
  const { ref, x, y } = useMouse();

  return (
    <>
      <Group justify="center">
        <Box ref={ref} w={300} h={180} bg="var(--mantine-color-blue-light)" />//refで指定された箱
      </Group>
      <Text ta="center">
        Mouse coordinates <Code>{`{ x: ${x}, y: ${y} }`}</Code>//マウスの位置をテキストで表示
      </Text>
    </>
  );
}

このコードではBoxの中でどこにマウスがあるかテキストで表示されます。

普通にtypescript(javascript)で取得するevent.screenXやevent.clientXとは何が違うのかというと、refで指定された箱の左上を(x,y) = (0,0)としてカウントしてくれます。
なので、box内でだけでマウスの情報を取得したいときに便利です。

https://mantine.dev/hooks/use-mouse/

use-scroll-into-view

スクロール可能な要素のスクロール動作をします。基本的な使い方は、element.scrollIntoView()と同じ。

作り方はこちら(コード内容はMantineのuse-scroll-into-viewのUsage)
import { useScrollIntoView } from '@mantine/hooks';
import { Button, Text, Group, Box } from '@mantine/core';

function Demo() {
  const { scrollIntoView, targetRef } = useScrollIntoView<HTMLDivElement>({
    offset: 60,
  });

  return (
    <Group justify="center">
      <Button
        onClick={() =>
          scrollIntoView({
            alignment: 'center',
          })
        }
      >
        Scroll to target
      </Button>
      <Box
        style={{
          width: '100%',
          height: '50vh',
          backgroundColor: 'var(--mantine-color-blue-light)',
        }}
      />
      <Text ref={targetRef}>Hello there</Text>//targetとなるText
    </Group>
  );
}

このコードはボタンを押すとtargetとして指定したもの(Text)を画面内に表示されるまでスクロールするものとなっています。

このようにtargetを決めてあるトリガーでそれが表示されるように自動スクロールされること
easingやdurationなどのプロパティをがタグ内にあるので読みやすいことがいいなと思っています。

一定値までスクロールしたら次のコンポーネントが表示されるまでスクロールするみたいな挙動が簡単に作れそう…

https://mantine.dev/hooks/use-scroll-into-view/

use-previous

一つ前の値をrefに格納します。

作り方はこちら(コード内容はMantineのuse-previousのUsage)
import { TextInput, Text } from '@mantine/core';
import { usePrevious, useInputState } from '@mantine/hooks';

function Demo() {
  const [value, setValue] = useInputState('');
  const previousValue = usePrevious(value);

  return (
    <div>
      <TextInput
        label="Enter some text here"
        placeholder="Enter some text here"
        id="previous-demo-input"
        value={value}
        onChange={setValue}
      />
      <Text mt="md">Current value: {value}</Text>
      <Text>Previous value: {previousValue}</Text>
    </div>
  );
}

「一つ前に戻る」の作成が簡単にできそうなhooksじゃん…って見た時思いました。(まだ実装はしたことないですが)
また使ってみてここは…的なことがあったら追記します!  
同じようなものにuse-state-historyというhookがあります。

https://mantine.dev/hooks/use-previous/

以上のhooks以外にもまだまだいっぱいhooksがあります。どのようなものがあるのか気になる方は、MantineのドキュメントからMantine Hooksを見てください!

オススメその3 ~form~

これには大変お世話になってます…
何をするかというと、 テキストやセレクトの状態を一括管理してくれたり、submitの関数が作りやすかったり… みたいな感じです。

作り方こちら(コード内容はMantineのuse-formのUsage)
import { Button, Checkbox, Group, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';

function Demo() {
  const form = useForm({
    mode: 'uncontrolled',
    initialValues: {
      email: '',
      termsOfService: false,
    },//初期値(useFormで管理した値は全て書くこと)

    validate: {
      email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
    },//submit時のerror確認処理(必要ない場合は書く必要なし)
  });

  return (
    <form onSubmit={form.onSubmit((values) => console.log(values))}>
    //form.onSubmit((values)=>handleOnSubmit(values))のように外にsubmitの処理を書くことも可能
      <TextInput
        withAsterisk
        label="Email"
        placeholder="your@email.com"
        key={form.key('email')}
        {...form.getInputProps('email')}
        //form.getInputProps(formで設定したinitialValuesのkey)とするとonChangeの処理とvalueの取得ができる
      />

      <Checkbox
        mt="md"
        label="I agree to sell my privacy"
        key={form.key('termsOfService')}
        {...form.getInputProps('termsOfService', { type: 'checkbox' })}
        //入力項目がcheckboxである場合、typeを追加する(defaultはinput)
      />

      <Group justify="flex-end" mt="md">
        <Button type="submit">Submit</Button>
      </Group>
    </form>
  );
}

コード内にコメントアウトで書いていますが、ミニマムな構成での作り方はこのようになっています。
テキストインプットやチェックボックスなどの入力以外で動的にvalueを変えたい場合、

form.setValues({email:'',termsOfService:true})

のようにすることで変更することができます。
このほかにもerrorを動的に表示したり、formのvalueを動的に取得したり…など色々できます。

個人的にプラスして便利だなと思ったのがvalidateの作り方です。

isNotEmptyやisEmail、matchesのような関数があり、簡単にvalueに対しての比較ができたり、引数をvalueだけでなく、form内全体のvaluesも取得できて他のvalueと比較できたりもします。

validate例
const form = useForm({
  ...,
  validate:{
    email: isEmail('invalid email'),
    password: isNotEmpty('password cannot be empty'),
    confirmPassword: (value, values) =>{
        value !== values.password ? "not confirm password" : null,
    }
  }
})

Uncontrolled mode

前提として、defaultやv7.8.0以前のformではデータをstateで管理しているため、フォームのデータが変更されるたびに同じ関数内(や子の関数)に書かれているコンポーネントが再レンダリングされます。

Uncontrolled modeとは、initialValuesやvalidateを作成していたところにmode:'uncontroled'とすると使える機能です。(defaultはcontrolledなので指定しない場合はcontrolledのmode)

uncontrolledにすると何がいいかというと、フォームのデータをrefで管理してくれるようになります。
大きなフォームや子の関数がある場合、再レンダリングに時間がかかったり…というような問題がrefだとそんなの関係ないですね!👏

ただし、uncontrolledにするとこれを書いてね!みたいなことがドキュメントに書いてあるので、必ずドキュメントを読んでから作成するようにしてください。

https://mantine.dev/form/uncontrolled/

オススメその4 ~charts~

データのグラフを扱うRechartsが追加されました!
使い方はRechartsのドキュメントに書いてあることとほとんど一緒で、サポートしているchartについてはMantine Chartsで確認することができます。
データの視覚化がよりしやすくなったことはとっても助かる…

https://mantine.dev/charts/getting-started/
https://recharts.org/en-US/api


まとめ

Mantineの素敵なのところの一部だけですが、読んでみて使いたくなったでしょうか?
書いてある内容以外にもthemeの設定やDatePickerなどがあります。
ここまで読んでしまった方はぜひドキュメントを一度見てみてくださいね🙌

誤字脱字や、内容が異なっていることに気づいた方はコメントしていただけると嬉しいです!

Discussion