Reactで親コンポーネントのStateを子コンポーネントから変更する

## 背景
Webアプリケーション開発中にReact+TypeScriptで下記のようなイメージのページを作りたいと思ったのですが，子コンポーネントのStateの変化(ここでは，`「表示数セレクタが選択されること」`)を親コンポーネントのレンダリングされる別コンポーネント(ここでは，`card`)の表示数に反映させる方法がわからず，調査したので，メモとして残します．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/99a227bb-fdcb-96bf-7e9a-d8e63f3abdb1.png)

(補足)今回やろうとしていることは，上記のイメージ図にあるように，子コンポーネントである表示数変更用のセレクタの変更をindexページ(親コンポーネント)に渡し，変更されたStateに応じて同じく子コンポーネントのcardコンポーネントの表示数をコントロールする実装です．


## 環境
TypeScript 4.9.4
React 18.2.0
(なお，webアプリケーションフレームワークには Ruby on Rails を使用しています)

## 実装
ポイントはpropsにState更新用のメソッド(`setValue`)を設定して，親コンポーネントから更新したいState(`count`)を更新するメソッド(`setCount`)を渡してあげることです．

- **子コンポーネント**
    - セレクタの実装はほとんどMaterial UIの実装をまねしました．
    [React Select component - Material UI](https://mui.com/material-ui/react-select/)
    ```tsx:BasicSelect.tsx
    import * as React from 'react';
    import Box from '@mui/material/Box';
    import InputLabel from '@mui/material/InputLabel';
    import MenuItem from '@mui/material/MenuItem';
    import FormControl from '@mui/material/FormControl';
    import Select, { SelectChangeEvent } from '@mui/material/Select';
    
    type Props = {
      value: number|string, // state
      setValue // callback （stateを変更するためのprops）
      options: Array<number | string>
    };
    
    export const BasicSelect = ({value, options, setValue}: Props) => {
      const handleChange = (event: SelectChangeEvent) => {
        setValue(event.target.value);  //親コンポーネントへのstateの引き渡し
      };
    
      return (
        <Box>
          <FormControl variant="filled" sx={{ m: 1, minWidth: 150 }}>
            <InputLabel id="select-label">表示数</InputLabel>
            <Select
              labelId="select-label"
              id="select"
              value={value}
              label="count"
              onChange={handleChange}
            >
              {options.map((option) => <MenuItem key={option} value={option}>{option}</MenuItem>)}  
            </Select>
          </FormControl>
        </Box>
      );
    }

- **親コンポーネント**
    ここでのカードの実装やindexページの実装もMaterial UIを頼らせていただきました．
    いくつか割愛している箇所もあるので，気になる方は下記のドキュメントをご覧ください．
    [React Grid component - Material UI](https://mui.com/material-ui/react-grid/)
    [React Card component - Material UI](https://mui.com/material-ui/react-card/)
    ```tsx:index.tsx
    import { MyCard } from 'app/javascript/components/MyCard/MyCard'; //表示するカードコンポーネント(ここの実装は割愛します．詳細は参考文献をご覧ください)
    import { BasicSelect } from 'app/javascript/components/BasicSelect/BasicSelect'; //子コンポーネントのインポート
    import { Grid,  Box} from '@mui/material';
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    export const Index = () => {
      const display_count_options = [1, 2, 3, 4]; //selector options
      const [count, setCount] = React.useState(2); //state変数，初期値と更新のための関数を宣言
      const { card_data } = getCardData(); //表示するCardに記載する内容を配列で取得(ここの実装は割愛します．詳細は参考文献をご覧ください)
      
      return (
        <>
          <BasicSelect value={count} setValue={setCount} options={display_count_options} /> 
          <br/>
          <Box sx={{ flexGrow: 1 }}>
            <Grid container spacing={2}>
              {card_data.slice(0,count).map((data) =>
                <Grid item xs={4} sx={{ padding: 0 }} key={data.name}>
                  <MyCard/>          
                </Grid>
              )}  
            </Grid>
          </Box>
        </>
      );
    };
    
    ReactDOM.render(<Index />, document.getElementById('Index'));

    ```

## 結果
思惑通り，セレクタの選択内容に応じてカードの表示数を変更できるようになりました．
![画面収録-2023-08-22-14.14.35.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/2859a247-13bf-e06d-3956-dabb353c3f28.gif)








## 参考文献

https://ja.legacy.reactjs.org/docs/hooks-state.html

- indexページの実装(CardとGridを組み合わせて表示するには...)
    [Material UIでカードを作成してグリッドで整理する方法｜廻遊者!!](https://kaiyuusya.jp/webLog/article/material-ui-card-and-grid)