質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

1557閲覧

[React + TypeScript] 開いているアコーディオンを閉じさせたい

black-ddd

総合スコア74

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/11/05 10:29

前提・実現したいこと

現在、サイト制作をしていくうえでアコーディオンのクリック部分での質問がありましたので、スレッドを立てました。
実現したいところといたしましては、開いているアコーディオンをクリックした時に閉じたい
という部分についてお聞きできればと思います。

#実際のコード

Parent

1import classes from './index.module.scss' 2import { Accordion } from './accordion' 3import { useCallback, useState } from 'react'; 4 5const accordionItems = [ 6 { 7 title: '', 8 text: [ 9 '', 10 ], 11 }, 12 { 13 title: '', 14 text: [ 15 '', 16 ], 17 }, 18 { 19 title: '', 20 text: [ 21 '', 22 '', 23 '', 24 ], 25 }, 26 { 27 title: '', 28 text: [ 29 '', 30 ], 31 }, 32 { 33 title: '', 34 text: [ 35 '', 36 ], 37 }, 38 { 39 title: '', 40 text: [ 41 '', 42 ], 43 }, 44 { 45 title: '', 46 text: [ 47 '', 48 ], 49 }, 50 { 51 title: '', 52 text: [ 53 '', 54 ], 55 }, 56] 57 58const defaultOpenIndex = 0; 59 60export const TopQuestion = () => { 61 62 const [openIndex, setIndex] = useState(defaultOpenIndex); 63 64 const changeIndex = useCallback((index: number) => { 65 setIndex(index); 66 }, []); 67 68 return ( 69 <div className={classes.wrapper}> 70 <div className={classes.contents}> 71 <div className={classes.about}> 72 <div className={classes.title}> 73 <h2>質問と回答</h2> 74 <p>より多くの質問と回答はFAQからご覧ください</p> 75 </div> 76 {[...accordionItems].map((item, i) => { 77 return ( 78 <div className={classes.accordionWrapper} key={i}> 79 <Accordion 80 title={item.title} 81 flag={openIndex === i} 82 changeIndex={changeIndex} 83 index={i} 84 > 85 {item.text.map((text, index) => { 86 return ( 87 <p key={index} className={classes.accordionText}> 88 {text} 89 </p> 90 ); 91 })} 92 </Accordion> 93 </div> 94 ); 95 })} 96 </div> 97 </div> 98 </div> 99 ); 100}

child

1import classes from "./index.module.scss"; 2import React from "react"; 3 4export type Props = { 5 title: string; 6 flag: boolean; 7 index: number; 8 changeIndex:(index: number) => void; 9 children: React.ReactNode; 10}; 11 12export const Accordion: React.VFC< 13Props 14> = React.memo(({title, index, changeIndex, flag, children}) => { 15 return ( 16 <div className={classes.accordionWrapper}> 17 <div 18 className={`${classes.accordionTitle} ${ 19 flag ? classes.open : "" 20 }`} 21 onClick={(e) => { 22 e.preventDefault(); 23 changeIndex(index); 24 }} 25 > 26 <p>{title}</p> 27 </div> 28 <div 29 className={`${classes.accordionItem} ${ 30 flag ? "" : classes.collapsed 31 }`} 32 > 33 <div className={classes.accordionContent}> 34 <div>{children}</div> 35 </div> 36 </div> 37 </div> 38 ); 39})

scss

1.wrapper { 2 width: 600px; 3 margin: 0 auto; 4} 5 6.accordionWrapper { 7 & + * { 8 margin-top: 0.5em; 9 } 10} 11 12.accordionItem { 13 overflow: hidden; 14 transition: max-height 0.3s cubic-bezier(1, 0, 1, 0); 15 height: auto; 16 max-height: 9999px; 17} 18 19.accordionItem.collapsed { 20 max-height: 0; 21 transition: max-height 0.35s cubic-bezier(0, 1, 0, 1); 22} 23 24.accordionTitle { 25 position: relative; 26 cursor: pointer; 27 color: #333; 28 padding: 0.5em 1.5em; 29 margin-bottom: 15px; 30 border-radius: 2px; 31 display: flex; 32 justify-content: space-between; 33 align-items: center; 34 transition: all 0.3s; 35 background-color: #fff; 36 box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2); 37 38 p { 39 font-size: 12px; 40 } 41 42 &::before, &::after { 43 content: ""; 44 display: block; 45 position: absolute; 46 top: 50%; 47 right: 50px; 48 width: 10px; 49 height: 2px; 50 border-radius: 5px; 51 background: #333; 52 opacity: 1; 53 transition: .4s; 54 } 55 56 &::before { 57 transform: rotate(90deg); 58 } 59 60 &:hover, 61 &.open { 62 color: #00bcd4; 63 &::before, &::after { 64 background: #00bcd4; 65 } 66 } 67 68 &.open { 69 &::before { 70 content: ""; 71 opacity: 0; 72 transition: .4s; 73 } 74 } 75} 76 77.accordionContent { 78 padding: 1em 1.5em; 79} 80 81.accordionContent > p { 82 font-size: 14px; 83} 84

#知りたいこと
現状は親要素でnuber型のuseStateを持ち、
openIndexがindex番号と同じならflagにtrueが入り、アコーディオンが開く設計にしています。
ただ、この設計だと初期画面で一つ目のアコーディオンが開き、他のアコーディオンを開くたびにもともと開いていたアコーディオンが閉まっていきますが、現状開いているアコーディオンをクリックしても閉まりません。
ここで何を実行すればアコーディオンが閉まるようになるのかのアドバイスをいただきたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

一番修正が少なそうな方法だと、changeIndexをこんな感じに修正すればいいと思います。

typescript

1const changeIndex = useCallback((index: number) => { 2 // indexとopenIndexが一致する場合、クリックした要素が開いているので-1をセットして 3 // どのアイテムにも一致しないようにする 4 if (index === openIndex) { 5 setIndex(-1); 6 } else { 7 setIndex(index); 8 } 9}, [openIndex]); // ここの第2引数にopenIndexを指定すること!

処理について説明すると、選択したアイテムのインデックスindexと、Stateで管理しているインデックスopenIndexが一致する場合はopenIndexに-1などのリストの番号とは一致しない値をセットするようにしているだけです。

これにより、リストの制御は下記の通りになります。

  • 開いているリストを選択した時
    -- openIndexがindexと一致するので-1がセットされる。
    -- 次の描画では、openIndexがどのアイテムの番号とも一致しないため全部閉じた状態になる
  • 開いているリストとは別のリストを選択した時
    -- openIndexがindexと一致しないので、indexの値がセットされる。
    -- 次の描画では、openIndexと一致するアイテムは開いた状態になる
  • 全部閉じている時に任意のリストを選択した時
    -- openIndexは-1なので、絶対にindexと一致しない。よってindexの値がセットされる
    -- 次の描画では、openIndexと一致するアイテムは開いた状態になる

また、1点注意として、useCallbackの第2引数に必ずopenIndexを指定してください。これを指定しないとopenIndexが更新されてもchangeIndex関数が更新されないため、changeIndex内で参照するopenIndexの値は最初の値(=defaultOpenIndex)から変化しなくなってしまいます。

投稿2021/11/07 05:00

ukyoda

総合スコア386

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問