1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React × Typescript】サイドバーの開閉をuseContextで状態管理する

Posted at

事前準備

こちらの記事を参考にヘッダーまで作成してみましょう!
以下が作成した画面です。(ダークモード設定時)
スクリーンショット 2023-02-04 16.54.22.png

ヘッダー部分とメニューボタンをコンポーネント化

ヘッダーのコンポーネント化

ヘッダーをコンポーネント化してトップ画面のコードを整理します。

src/components/pages/top/top.tsx
import * as React from 'react';
import { Header } from '../../model/header/header';

export const Top = () => {
  return (
    <>
      <Header />
    </>
  );
}

ヘッダーコンポーネントのコードは以下の通りです。

src/components/model/header/header.tsx
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';

export const Header = () => {
  return (
    <Box sx={{ flexGrow: 1 }}>
      <AppBar position="static">
        <Toolbar>
          <IconButton
            size="large"
            edge="start"
            color="inherit"
            aria-label="menu"
            sx={{ mr: 2 }}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
            News
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </Box>
  );
}

これでヘッダーのコンポーネント化は完了です。

メニューボタンのコンポーネント化

メニューボタンをコンポーネント化して、ヘッダーのコードを整理します。

src/components/ui/button/menuButton.tsx
import * as React from 'react';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';

export function MenuButton() {
    return (
        <>
            <IconButton
                size="large"
                edge="start"
                color="inherit"
                aria-label="menu"
                sx={{ mr: 2 }}
            >
                <MenuIcon />
            </IconButton>
        </>
    );
 }
src/components/model/header/header.tsx
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import { MenuButton } from '../../ui/button/menuButton';

export const Header = () => {
  return (
    <Box sx={{ flexGrow: 1 }}>
      <AppBar position="static">
        <Toolbar>
          <MenuButton />
          <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
            News
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </Box>
  );
}

これでメニューボタンのコンポーネント化は完了です。

サイドバー開閉の実装

useContextについて

まずはサイドバー開閉の状態を管理するuseContextについて説明します。
useContext とは Context 機能をよりシンプルに使えるようになった React 独自の機能です。
簡単に言うと、親から props で渡されていないのに、Context に収容されているデータにアクセスできるというものです。

Context とは
props とは別の方法でコンポーネントに動的に値を渡す、 React 独自のAPIです。
props のバケツリレーを回避するために存在します。

useContextの実装

createContext メソッドを利用し、サイドバー開閉の状態を管理する menuContext を作成します。

src/hooks/appState.ts
import { createContext } from "react";

export const menuContext = createContext({
    isOpened: false,
    setOpened: (isOpen: boolean) => {}
});

menuContextを参照する

上記で作成した menuContext を参照します。

src/components/ui/button/menuButton.tsx
import * as React from 'react';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';
import { useContext } from 'react';
import { menuContext } from '../../../hooks/appState';

export function MenuButton() {
    const { isOpened, setOpened } = useContext(menuContext);
    const toggleOpen = () => setOpened(!isOpened);

    return (
        <>
            <IconButton
                size="large"
                edge="start"
                color="inherit"
                aria-label="open drawer"
                onClick={toggleOpen}
                sx={{ mr: 2 }}
            >
                <MenuIcon />
            </IconButton>
        </>
    );
 }

onClick イベントを追加することでヘッダーにあるメニューボタンをクリックした際に toggleOpen メソッドが呼び出され、menuContext の値が変化します。

サイドバーの作成

開閉するサイドバーを作成します。

src/components/model/drawer/drawer.tsx
import * as React from 'react';
import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer';
import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import { ListItemText } from '@mui/material';

type Props = {
    open: boolean;
};

export const DesktopDrawer: React.FC<Props> = (props) => {
    return (
        <>
            <Drawer
                sx={{
                    width: 200,
                    flexShrink: 0,
                    '& .MuiDrawer-paper': {
                        width: 200,
                    },
                }}
                variant="persistent"
                anchor="left"
                open={props.open}
            >
                <Toolbar />
                <Box sx={{overflow: "auto"}}>
                    <List>
                        <ListItemButton>
                        <ListItemText
                            sx={{ my: 0 }}
                            primary="home"
                            primaryTypographyProps={{
                                fontSize: 18,
                                fontWeight: 'medium',
                                letterSpacing: 0,
                            }}
                        >
                        </ListItemText>
                        <ListItem />
                        </ListItemButton>
                    </List>
                </Box>
            </Drawer>
        </>
    );
};

サイドバー側では、drawer の開閉状態をシンプルに props で受け取るようにします。

トップ画面にサイドバー開閉を実装

トップ画面に作成したサイドバー開閉を反映させます。

src/components/pages/top/top.tsx
import * as React from 'react';
import { Header } from '../../model/header/header';
import { menuContext } from '../../../hooks/appState';
import { DesktopDrawer } from '../../model/drawer/drawer';

export const Top = () => {
    const [isOpened, setOpened] = React.useState(true);

    return (
        <>
            <menuContext.Provider value={{isOpened, setOpened}}>
                    <Header />
                    <DesktopDrawer open={isOpened} />
            </menuContext.Provider>
        </>
    );
 }

トップ画面では、menuContextProvider を通じて ReactContext を利用できるようにします。
その上で、開閉状態を読み取るコンポーネントである DesktopDrawer には isOpened プロパティを渡します。

Provider とは
全てのコンテクストオブジェクト(Context)にはプロバイダ(Provider)コンポーネントが付属しています。
Provider コンポーネントは、props として value を受け取ります。
この value に入れるのは、バケツリレーしていた値です。

作成した画面は以下の通りになります。
スクリーンショット 2023-02-04 18.11.02.png
あ…
ヘッダーが隠れてしまいました…
これは私もつまずいた所で、完成したと思いきやこの画面が出てきた時は絶望しました(笑)
でも安心して下さい(笑)解決したからこの記事を書いています。
ヘッダーコンポーネントを以下のように修正すれば解決します。

src/components/model/header/header.tsx
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import { MenuButton } from '../../ui/button/menuButton';

export const Header = () => {
  return (
    <Box sx={{ flexGrow: 1 }}>
        <AppBar
            position="fixed"
            sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
        >
        <Toolbar>
          <MenuButton />
          <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
            News
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </Box>
  );
}

修正した箇所は、 <AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }} >
です。
drewer open.gif
これでヘッダーに隠れず、サイドバーの開閉が可能になりました!

1
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?