RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

React BootstrapのAccordionで複数のAccordion Collapseを同時に開閉するボタンを作る

こんにちは。RITの関です。

React BootstrapのAccordion.Collapseを同時に開閉するボタンを作るという実装に少し苦労したので、その方法を紹介します。

※この記事はreact-bootstrap 1.6版の実装方法です。2.~版はアコーディオン周りの仕組みが異なっているためこの記事は参考にしないでください。

完成イメージ

完成イメージ
完成イメージ

まずはシンプルなAccordionを作る

test_table.tsx

import { TestList } from '@/Presentation/Component/Home/TestList';
import { NextPage } from 'next';
import React from 'react';

const TestTable: NextPage = () => {
    return (
        <div>
            <div className='mb-4 d-flex align-items-center justify-content-between'>
                <p className='mb-0 font-weight-bold'>アコーディオン一覧</p>
            </div>
            <div>
                {[
                    'accordionTextあああああ',
                    'accordionTextいいいいい',
                    'accordionTextううううう',
                ].map((text, index) => (
                    <div key={index}>
                        <TestList
                            eventKey={index.toString()}
                            accordionText={text}
                        />
                    </div>
                ))}
            </div>
        </div>
    );
};

export default TestTable;

test_list.tsx

import React, { FunctionComponent } from 'react';
import { ListGroup, Button, Accordion, Card } from 'react-bootstrap';

interface Props {
    eventKey: string;
    accordionText: string;
}

export const TestList: FunctionComponent<Props> = props => {
    return (
        <Accordion>
            <Card className='border-0 rounded-0'>
                <Accordion.Toggle as={Button} eventKey={props.eventKey}>
                    <div className='d-flex justify-content-between align-items-center'>
                        {props.accordionText}
                    </div>
                </Accordion.Toggle>
                <Accordion.Collapse eventKey={props.eventKey}>
                    <ListGroup>
                        <p>リストの中身だよ</p>
                    </ListGroup>
                </Accordion.Collapse>
            </Card>
        </Accordion>
    );
};

React Bootstrapのチュートリアルに書いてあるようなシンプルなアコーディオンを作成しました。

簡易アコーディオン
簡易アコーディオン

諸々編集する

次は、諸々更新したtest_tableをまとめて載せます。大きくやったことは下記です。

①openというstateの追加
AccordionOpenという型定義を追加して、openというstateを追加しました。
openの中身は全てを開くか全てを閉じるか判断するisOpenと、ボタンを2回以上連続クリックしても反応してくれるようにtimestampを入れています。
もしtimestampを入れない場合、isOpenの値が変化しないことから、全てを開くボタンが動作してくれなくなります。

②test_listへ渡すpropsにopenを追加
test_listがopenを受け取れるようにして、受け取ったpropsによって全アコーディオンを開閉するようにしています。

③test_list自体にisShowというstateを追加して、Accordion自体をisShowを元に開閉するように変更
Accordion activeKey={isShow ? props.eventKey : ''}と定義することで、isShow: trueのときはactiveKeyとprops.eventKeyが同じ値になるので開く、isShow: falseのときはactiveKeyとprops.eventKeyが異なる値になるので閉じるようになります。
この開閉の仕組自体はreact-bootstrap自体によるものなので、詳細は調べてみてください。

④test_listにuseEffectを追加
test_listにuseEffectを追加して、props.openが変わるごとにisShowの値をprops.openと同じにするようにしています。
これによって、全てを開くボタンを押した時に、openの値がtrueになる→test_listのisShowもtrueになる→アコーディオンが開くという処理ができるようになります!

test_table.tsx

export interface AccordionOpen {
    isOpen: boolean;
    timestamp: number;
}

const TestTable: NextPage = () => {
    const [open, setOpen] = useState<AccordionOpen>({
        isOpen: true,
        timestamp: new Date().getTime(),
    });

    return (
        <div>
            <div className='mb-4 d-flex align-items-center justify-content-between'>
                <p className='mb-0 font-weight-bold'>コンタクト一覧</p>
                <div>
                    <Button
                        variant='outline-primary mr-3'
                        onClick={() =>
                            setOpen({
                                isOpen: true,
                                timestamp: new Date().getTime(),
                            })
                        }
                    >
                        すべてを表示
                    </Button>
                    <Button
                        variant='outline-primary'
                        onClick={() =>
                            setOpen({
                                isOpen: false,
                                timestamp: new Date().getTime(),
                            })
                        }
                    >
                        すべてを閉じる
                    </Button>
                </div>
            </div>
            <div>
                {[
                    'accordionTextあああああ',
                    'accordionTextいいいいい',
                    'accordionTextううううう',
                ].map((text, index) => (
                    <div key={index}>
                        <TestList
                            eventKey={index.toString()}
                            accordionText={text}
                            open={open}
                        />
                    </div>
                ))}
            </div>
        </div>
    );
};

export default TestTable;

test_list.tsx

interface Props {
    eventKey: string;
    accordionText: string;
    open: AccordionOpen;
}

export const TestList: FunctionComponent<Props> = props => {
    const [isShow, setIsShow] = useState(true);

    useEffect(() => {
        setIsShow(props.open.isOpen);
    }, [props.open]);

    return (
        <Accordion activeKey={isShow ? props.eventKey : ''}>
            <Card className='border-0 rounded-0'>
                <Accordion.Toggle
                    as={Button}
                    eventKey={props.eventKey}
                    className='px-4 py-2 text-left'
                    onClick={() => {
                        setIsShow(!isShow);
                    }}
                >
                    <div className='d-flex justify-content-between align-items-center'>
                        {props.accordionText}
                    </div>
                </Accordion.Toggle>
                <Accordion.Collapse eventKey={props.eventKey}>
                    <ListGroup>
                        <p>リストの中身だよ</p>
                    </ListGroup>
                </Accordion.Collapse>
            </Card>
        </Accordion>
    );
};

最後に動きのイメージを貼って終わりにします。react-bootstrapを使うときの役に立てると幸いです。ありがとうございました!

完成イメージ

完成イメージ
完成イメージ