【React】useState(デモコンポーネント:続きを読むボタン)

Stateの概念とは

それぞれのコンポーネントが持っている状態です。

  • エラーがあるのか、ないのか
  • モーダルが開いているのか、いないのか
  • ユーザーが入力した内容
React Component useState State setState() Read Update Trigger Re-render

useStateの使い方

1)「useState」をインポート

「React」からフック「useState」をインポートします

import { useState } from "react";

→関数「useState();」が使えるようになります

2)useStateの定義

配列の分割代入でうけとります

const [num, setNum] = useState();
// 配列の分割代入なので変数は任意の名前でOK

▼初期値の設定もできます

const [num, setNum] = useState(0);

3)ステートの中身を更新する

2)で定義した変数を使います

setNum(1);

実装例

続きを読むボタン

page.tsx

import React from "react";
import ReadMore from "@/components/readMore";
export default function Page() {
    return (
        <>
            <ReadMore>
                <p>続きを読むボタンの例</p>
                <br />
                <p>テキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
                <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
                <p>テキストテキストテキスト</p>
                <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
                <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
                <p>テキストテキストテキスト</p>
                <p>テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
                <p>テキストテキストテキスト</p>
                <p>テキストテキストテキストテキストテキストテキストテキストテキストテキスト</p>
                <p>テキストテキストテキスト</p>
            </ReadMore>
        </>
    );
}

readMore.tsx

'use client';
import React, { useEffect, useRef, useState } from 'react';
import styles from '@/styles/readMore.module.css';
interface ReadMoreProps {
  children: React.ReactNode;
}
const ReadMore: React.FC<ReadMoreProps> = ({ children }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const contentRef = useRef<HTMLDivElement>(null);
  const [maxHeight, setMaxHeight] = useState<number | null>(null);
  useEffect(() => {
    if (contentRef.current) {
      setMaxHeight(contentRef.current.scrollHeight); //scrollHeight: 要素のスクロールビューの高さを取得
    }
  }, [children]);
  const toggleReadMore = () => {
    setIsExpanded(!isExpanded);
  };
  return (
    <div className={styles.readMoreWrapper}>
      <div 
        className={`${styles.content} ${isExpanded ? styles.expanded : ''}`}
        style={maxHeight ? { '--max-height': `${maxHeight}px`} as React.CSSProperties : {}}
      >
        <div
          ref={contentRef}
          className={`${styles['grad-item']} ${isExpanded ? styles['no-gradient'] : ''}`}>
          {children}
        </div>
      </div>
      <button className={styles.readMoreButton} onClick={toggleReadMore}>
        {isExpanded ? '閉じる' : '続きを読む'}
      </button>
    </div>
  );
};
export default ReadMore;
ReadMoreコンポーネント (useStateフォーカス) useState in ReadMore: const [isExpanded, setIsExpanded] = useState(false); const [maxHeight, setMaxHeight] = useState<number | null>(null); コンテンツレンダリング: <div className={`${styles.content} ${isExpanded ? styles.expanded : ”}`} style={maxHeight ? { ‘–max-height’: `${maxHeight}px`} : {}}> <div ref={contentRef} className={`${styles[‘grad-item’]} ${isExpanded ? styles[‘no-gradient’] : ”}`}> {children} </div> </div> ボタンと切り替え関数: const toggleReadMore = () => setIsExpanded(!isExpanded); <button onClick={toggleReadMore}>{isExpanded ? ‘閉じる’ : ‘続きを読む’}</button>

readMore.module.css

.readMoreWrapper {
    margin: 0 auto;
    max-width: 800px;
    padding: 20px;
    border: solid 1px #5599ee;
    background-color: #fff;
  }
  
  .readMoreWrpapper h2 {
    margin-bottom: 20px;
  }
  
  .readMoreWrpapper p {
    margin-bottom: 20px;
}
.content {
    max-height: 200px; /* 初期表示の高さ */
    overflow: hidden;
    position: relative;
    transition: max-height 0.3s ease;
    padding-bottom: 1rem;
  }
  
  .content.expanded {
    max-height: var(--max-height, 1000px);
  }
  
  .grad-item::before {
    display: block;
    position: absolute;
    bottom: 0;
    left: 0;
    content: "";
    width: 100%;
    height: 100px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 50%, rgba(255, 255, 255, 0.9) 50%, #fff 100%);
  }
  .grad-item.no-gradient::before {
    display: none;
  }
  
  .readMoreButton {
    display: block;
    min-width: 160px;
    border-radius: 999px;
    margin: 10px auto;
    padding: 10px 20px;
    background-color: #006999;
    border: 1px solid #006999;
    color: #fff;
    border: none;
    cursor: pointer;
    transition: background-color 0.3s ease, color 0.3s ease;
  }
  
  .readMoreButton:hover {
    background-color: #fff;
    border: 1px solid #006999;
    color: #006999;
  }

参考サイト

ReactのuseStateについて丁寧に理解する
https://qiita.com/KokiSakano/items/c16a8daf03acdbd6c911

https://qiita.com/poppy_83/items/a9784d873a261f67f472