【Chakra UI v3 × jest】テスト時のSelectの型エラーの解消方法②（Type '{ children: ReactNode; ref: ForwardedRef<HTMLButtonElement>; }' is not assignable to type 'IntrinsicAttributes & SelectTriggerProps & RefAttributes<HTMLButtonElement>'.他）

# はじめに

お疲れ様です、りつです。

前回に引き続き、Chakra UIの型エラーについてです。

- 前回の記事

https://qiita.com/ritsu21ctws/items/eda3d890a26dd0d1a4f4

# 問題

テスト実行時に以下のエラーが発生しました。

```bash:エラー内容
 FAIL  src/__tests__/sampleComponent.spec.tsx
  ● Test suite failed to run

    src/components/ui/select.tsx:17:8 - error TS2322: Type '{ children: ReactNode; ref: ForwardedRef<HTMLButtonElement>; }' is not assignable to type 'IntrinsicAttributes & SelectTriggerProps & RefAttributes<HTMLButtonElement>'.
      Property 'children' does not exist on type 'IntrinsicAttributes & SelectTriggerProps & RefAttributes<HTMLButtonElement>'.

    17       <ChakraSelect.Trigger ref={ref}>{children}</ChakraSelect.Trigger>
              ~~~~~~~~~~~~~~~~~~~~
    src/components/ui/select.tsx:28:6 - error TS2322: Type '{ children: Element; ref: ForwardedRef<HTMLButtonElement>; asChild: true; }' is not assignable to type 'IntrinsicAttributes & SelectClearTriggerProps & RefAttributes<HTMLButtonElement>'.
      Property 'children' does not exist on type 'IntrinsicAttributes & SelectClearTriggerProps & RefAttributes<HTMLButtonElement>'.

    28     <ChakraSelect.ClearTrigger asChild {...props} ref={ref}>
            ~~~~~~~~~~~~~~~~~~~~~~~~~
    src/components/ui/select.tsx:60:6 - error TS2322: Type '{ children: (ReactNode | Element)[]; ref: ForwardedRef<HTMLDivElement>; key: any; item: CollectionItem; }' is not assignable to type 'IntrinsicAttributes & SelectItemProps & RefAttributes<HTMLDivElement>'.
      Property 'children' does not exist on type 'IntrinsicAttributes & SelectItemProps & RefAttributes<HTMLDivElement>'.

    60     <ChakraSelect.Item key={item.value} item={item} {...rest} ref={ref}>
            ~~~~~~~~~~~~~~~~~
    src/components/ui/select.tsx:75:6 - error TS2322: Type '{ children: Element; ref: ForwardedRef<HTMLSpanElement>; placeholder: string; }' is not assignable to type 'IntrinsicAttributes & SelectValueTextProps & RefAttributes<HTMLSpanElement>'.
      Property 'children' does not exist on type 'IntrinsicAttributes & SelectValueTextProps & RefAttributes<HTMLSpanElement>'.

    75     <ChakraSelect.ValueText {...rest} ref={ref}>
            ~~~~~~~~~~~~~~~~~~~~~~
    src/components/ui/select.tsx:109:11 - error TS2339: Property 'children' does not exist on type 'SelectItemGroupProps'.

    109   const { children, label, ...rest } = props;
                  ~~~~~~~~
    src/components/ui/select.tsx:111:6 - error TS2322: Type '{ children: any[]; ref: ForwardedRef<HTMLDivElement>; }' is not assignable to type 'IntrinsicAttributes & SelectItemGroupProps & RefAttributes<HTMLDivElement>'.
      Property 'children' does not exist on type 'IntrinsicAttributes & SelectItemGroupProps & RefAttributes<HTMLDivElement>'.

    111     <ChakraSelect.ItemGroup {...rest} ref={ref}>
             ~~~~~~~~~~~~~~~~~~~~~~

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        5.729 s
Ran all test suites.
```

<details><summary>参考ファイル</summary>

```src/App.tsx
import { Router } from '@/router/Router';
import { Toaster } from '@/components/ui/toaster';

function App() {
  return (
    <>
      <Router />
      <Toaster />
    </>
  );
}

export default App;
```

```react:src/components/pages/Register.tsx
import React, { memo, useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import { Controller, useForm } from 'react-hook-form';
import { Button, Card, Center, createListCollection, Heading, Textarea, Input, Stack, ListCollection } from '@chakra-ui/react';
import { Field } from '@/components/ui/field';
import { SelectContent, SelectItem, SelectRoot, SelectTrigger, SelectValueText } from '@/components/ui/select';
import { FormData } from '@/domain/formData';
import { useMessage } from '@/hooks/useMessage';
import { fetchSkills, insertUser } from '@/utils/supabaseFunctions';

export const Register: React.FC = memo(() => {
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    defaultValues: {
      github_id: '',
      qiita_id: '',
      x_id: '',
    },
  });
  const navigate = useNavigate();
  const { showMessage } = useMessage();

  const [skills, setSkills] = useState<ListCollection<{ label: string; value: string }> | null>(null);

  useEffect(() => {
    fetchSkills()
      .then((data) => {
        setSkills(createListCollection({ items: data.map((skill) => ({ label: skill.name, value: skill.id.toString() })) }));
      })
      .catch(() => {
        showMessage({ title: 'データの取得に失敗しました', type: 'error' });
      });
  }, []);

  const onSubmit = handleSubmit((data: FormData) => {
    const transformedData = {
      ...data,
      github_id: data.github_id || null,
      qiita_id: data.qiita_id || null,
      x_id: data.x_id || null,
    };
    insertUser(transformedData)
      .then(() => {
        showMessage({ title: '登録が完了しました', type: 'success' });
        navigate('/');
      })
      .catch(() => {
        showMessage({ title: '登録に失敗しました', type: 'error' });
      });
  });

  return (
    <>
      <Center my="5">
        <Stack>
          <Heading as="h1" mb="2" textAlign="center">
            名刺新規登録
          </Heading>
          <Card.Root width="340px" variant="elevated">
            <form onSubmit={onSubmit}>
              <Card.Body>
                <Stack gap="4" w="full">
                  <Field label="好きな英単語 *" invalid={!!errors.user_id} errorText={errors.user_id?.message}>
                    <Controller
                      name="user_id"
                      control={control}
                      rules={{
                        required: '好きな英単語の入力は必須です',
                        pattern: { value: /^[a-zA-Z]+$/, message: '好きな英単語は半角英字で入力してください。' },
                      }}
                      render={({ field }) => <Input {...field} placeholder="coffee" />}
                    />
                  </Field>
                  <Field label="名前 *" invalid={!!errors.name} errorText={errors.name?.message}>
                    <Controller
                      name="name"
                      control={control}
                      rules={{
                        required: '名前の入力は必須です',
                      }}
                      render={({ field }) => <Input {...field} />}
                    />
                  </Field>
                  <Field label="自己紹介 *" invalid={!!errors.description} errorText={errors.description?.message}>
                    <Controller
                      name="description"
                      control={control}
                      rules={{
                        required: '自己紹介の入力は必須です',
                      }}
                      render={({ field }) => <Textarea {...field} placeholder="<h1>HTMLタグも使えます</h1>" />}
                    />
                  </Field>
                  <Field label="好きな技術" invalid={!!errors.skills} errorText={errors.skills?.message}>
                    <Controller
                      name="skills"
                      control={control}
                      rules={{
                        required: '好きな技術の入力は必須です',
                      }}
                      render={({ field }) => (
                        <SelectRoot
                          name={field.name}
                          value={field.value}
                          onValueChange={({ value }) => field.onChange(value)}
                          onInteractOutside={() => field.onBlur()}
                          multiple
                          collection={skills || createListCollection({ items: [] })}
                        >
                          <SelectTrigger>
                            <SelectValueText placeholder="Select Option" />
                          </SelectTrigger>
                          <SelectContent>
                            {skills?.items.map((skill) => (
                              <SelectItem item={skill} key={skill.value}>
                                {skill.label}
                              </SelectItem>
                            ))}
                          </SelectContent>
                        </SelectRoot>
                      )}
                    />
                  </Field>
                  <Field label="GitHub ID">
                    <Controller name="github_id" control={control} render={({ field }) => <Input {...field} value={field.value || ''} />} />
                  </Field>
                  <Field label="Qiita ID">
                    <Controller name="qiita_id" control={control} render={({ field }) => <Input {...field} value={field.value || ''} />} />
                  </Field>
                  <Field label="X ID">
                    <Controller
                      name="x_id"
                      control={control}
                      render={({ field }) => <Input {...field} value={field.value || ''} placeholder="@は不要" />}
                    />
                  </Field>
                  *は必須項目です
                </Stack>
              </Card.Body>
              <Card.Footer justifyContent="flex-end">
                <Button variant="solid" type="submit" aria-label="Submit" colorPalette="cyan" w="full">
                  登録
                </Button>
              </Card.Footer>
            </form>
          </Card.Root>
        </Stack>
      </Center>
    </>
  );
});
```

```src/__tests__/sampleComponent.spec.tsx
import App from '../App';
import { render, screen } from '@testing-library/react';
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
import { BrowserRouter } from 'react-router';

describe('App', () => {
  test('タイトルがあること', async () => {
    render(
      <BrowserRouter>
        <ChakraProvider value={defaultSystem}>
          <App />
        </ChakraProvider>
      </BrowserRouter>
    );
    const title = screen.getByTestId('title');
    expect(title).toBeInTheDocument();
  });
});
```

</details>

# 解決方法

`src/components/ui/select.tsx`の内容を修正します。
なお、コメントの番号は、何番目のエラーに対応するかを表しています。

```diff_tsx:src/components/ui/select.tsx
'use client';

- import type { CollectionItem, SelectItemProps } from '@chakra-ui/react';
+ import type { CollectionItem, SelectItemProps, SelectClearTriggerProps } from '@chakra-ui/react'; // 2. SelectClearTriggerPropsをインポート
import { Select as ChakraSelect, Portal } from '@chakra-ui/react';
import { CloseButton } from './close-button';
import * as React from 'react';

interface SelectTriggerProps extends ChakraSelect.ControlProps {
  clearable?: boolean;
  children?: React.ReactNode;
}

export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(function SelectTrigger(props, ref) {
  const { children, clearable, ...rest } = props;
+ const chakraSelectTriggerProps: = { // 1. chakraSelectTriggerPropsを定義
+   children,
+ };
  return (
    <ChakraSelect.Control {...rest}>
-     <ChakraSelect.Trigger ref={ref}>{children}</ChakraSelect.Trigger>
+     <ChakraSelect.Trigger ref={ref} {...chakraSelectTriggerProps} /> {/* 1. chakraSelectTriggerPropsに置き換え */}
      <ChakraSelect.IndicatorGroup>
        {clearable && <SelectClearTrigger />}
        <ChakraSelect.Indicator />
      </ChakraSelect.IndicatorGroup>
    </ChakraSelect.Control>
  );
});

const SelectClearTrigger = React.forwardRef<HTMLButtonElement, ChakraSelect.ClearTriggerProps>(function SelectClearTrigger(props, ref) {
+ const chakraSelectClearTriggerProps: SelectClearTriggerProps = { // 2. chakraSelectClearTriggerPropsを定義
+   asChild: true,
+   children: <CloseButton size="xs" variant="plain" focusVisibleRing="inside" focusRingWidth="2px" pointerEvents="auto" />,
+ };
- return (
-   <ChakraSelect.ClearTrigger asChild {...props} ref={ref}>
-     <CloseButton size="xs" variant="plain" focusVisibleRing="inside" focusRingWidth="2px" pointerEvents="auto" />
-   </ChakraSelect.ClearTrigger>
- );
+ return <ChakraSelect.ClearTrigger {...props} ref={ref} {...chakraSelectClearTriggerProps} />; {/* 2. chakraSelectClearTriggerPropsに置き換え */}
});

interface SelectContentProps extends ChakraSelect.ContentProps {
  portalled?: boolean;
  portalRef?: React.RefObject<HTMLElement>;
  children?: React.ReactNode;
}

export const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(function SelectContent(props, ref) {
  const { portalled = true, portalRef, ...rest } = props;
  return (
    <Portal disabled={!portalled} container={portalRef}>
      <ChakraSelect.Positioner>
        <ChakraSelect.Content {...rest} ref={ref} />
      </ChakraSelect.Positioner>
    </Portal>
  );
});

interface NewSelectItemProps extends SelectItemProps {
  children?: React.ReactNode;
  item: CollectionItem;
  key?: string;
}

export const SelectItem = React.forwardRef<HTMLDivElement, NewSelectItemProps>(function SelectItem(props, ref) {
  const { item, children, ...rest } = props;
+ const chakraSelectItemProps: NewSelectItemProps = { // 3. chakraSelectItemPropsを定義
+   children: (
+     <>
+       {children}
+       <ChakraSelect.ItemIndicator />
+     </>
+   ),
+   item,
+ };
- return (
-   <ChakraSelect.Item key={item.value} item={item} {...rest} ref={ref}>
-     {children}
-     <ChakraSelect.ItemIndicator />
-   </ChakraSelect.Item>
- );
+ return <ChakraSelect.Item key={item.value} {...rest} ref={ref} {...chakraSelectItemProps} />; {/* 3. chakraSelectItemPropsに置き換え */}
});

interface SelectValueTextProps extends Omit<ChakraSelect.ValueTextProps, 'children'> {
  children?(items: CollectionItem[]): React.ReactNode;
  placeholder: string;
}

export const SelectValueText = React.forwardRef<HTMLSpanElement, SelectValueTextProps>(function SelectValueText(props, ref) {
  const { children, ...rest } = props;
+ const chakraSelectValueTextProps = { // 4. chakraSelectValueTextPropsを定義
+   children: (
+     <ChakraSelect.Context>
+       {(select) => {
+         const items = select.selectedItems;
+         if (items.length === 0) return props.placeholder;
+         if (children) return children(items);
+         if (items.length === 1) return select.collection.stringifyItem(items[0]);
+         return `${items.length} selected`;
+       }}
+     </ChakraSelect.Context>
+   ),
+ };
- return (
-   <ChakraSelect.ValueText {...rest} ref={ref}>
-     <ChakraSelect.Context>
-       {(select) => {
-         const items = select.selectedItems;
-         if (items.length === 0) return props.placeholder;
-         if (children) return children(items);
-         if (items.length === 1) return select.collection.stringifyItem(items[0]);
-         return `${items.length} selected`;
-       }}
-     </ChakraSelect.Context>
-   </ChakraSelect.ValueText>
- );
+ return <ChakraSelect.ValueText {...rest} ref={ref} {...chakraSelectValueTextProps} />; {/* 4. chakraSelectValueTextPropsに置き換え */}
});

export const SelectRoot = React.forwardRef<HTMLDivElement, ChakraSelect.RootProps>(function SelectRoot(props, ref) {
  return (
    <ChakraSelect.Root {...props} ref={ref} positioning={{ sameWidth: true, ...props.positioning }}>
      {props.asChild ? (
        props.children
      ) : (
        <>
          <ChakraSelect.HiddenSelect />
          {props.children}
        </>
      )}
    </ChakraSelect.Root>
  );
}) as ChakraSelect.RootComponent;

interface SelectItemGroupProps extends ChakraSelect.ItemGroupProps {
  label: React.ReactNode;
+ children: React.ReactNode; // 5. SelectItemGroupPropsにchildrenを追加
}

export const SelectItemGroup = React.forwardRef<HTMLDivElement, SelectItemGroupProps>(function SelectItemGroup(props, ref) {
  const { children, label, ...rest } = props;
+ const chakraSelectItemGroupProps = { // 6. chakraSelectItemGroupPropsを定義
+   children: (
+     <>
+       <ChakraSelect.ItemGroupLabel>{label}</ChakraSelect.ItemGroupLabel>
+       {children}
+     </>
+   ),
+ };
- return (
-   <ChakraSelect.ItemGroup {...rest} ref={ref}>
-     <ChakraSelect.ItemGroupLabel>{label}</ChakraSelect.ItemGroupLabel>
-     {children}
-   </ChakraSelect.ItemGroup>
- );
+ return <ChakraSelect.ItemGroup {...rest} ref={ref} {...chakraSelectItemGroupProps} />; {/* 6. chakraSelectItemGroupPropsに置き換え */}
});

export const SelectLabel = ChakraSelect.Label;
export const SelectItemText = ChakraSelect.ItemText;
```

# おわりに

以上の修正により、テストが通るようになりました。

# 参考

https://www.chakra-ui.com/docs/components/select