AppwriteAPI「TypeError: Cannot read properties of undefined (reading 'split')」調査報告 📄

## はじめに
TypeScript・React学習コンテンツにて、ユーザー登録機能に`Appwrite`の`Create userAPI`を使用した所、以下`TypeError`になったためその調査過程を記しておきます。
```
TypeError: Cannot read properties of undefined (reading 'split')
```
![スクリーンショット 2025-03-30 23.35.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3427082/2d875372-8c06-47ba-9b44-ba597cdcee4b.png)

## 目次
1. [開発環境](#-開発環境)
2. [調査で触れる関連ソース](#-調査で触れる関連ソース)
3. [情報収集 Part1](#-情報収集-part1)
4. [情報収集 Part2](#-情報収集-part2)
5. [解析](#-解析)
6. [仮説](#-仮説)
7. [検証](#-検証)
8. [まとめ](#-まとめ)
9. [参考文献](#-参考文献)

## 🧷 開発環境
* バックエンド・DB：Typescript、Appwrite
* フロントエンド：HTML、TailwindCSS、React
* フレームワーク：Next.js

## 🧷 調査で触れる関連ソース

### package.json
```tsx
{
  "name": "carepulse",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.10.0",
    "@radix-ui/react-alert-dialog": "^1.0.5",
    "@radix-ui/react-checkbox": "^1.0.4",
    "@radix-ui/react-dialog": "^1.0.5",
    "@radix-ui/react-label": "^2.1.2",
    "@radix-ui/react-popover": "^1.0.7",
    "@radix-ui/react-radio-group": "^1.1.3",
    "@radix-ui/react-select": "^2.0.0",
    "@radix-ui/react-separator": "^1.0.3",
    "@radix-ui/react-slot": "^1.1.2",
    "@sentry/nextjs": "^8.9.2",
    "@tanstack/react-table": "^8.17.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.0.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-config-standard": "^17.1.0",
    "eslint-plugin-tailwindcss": "^3.15.1",
    "fs": "^0.0.1-security",
    "input-otp": "^1.2.4",
    "lucide-react": "^0.378.0",
    "next": "^14.2.26",
    "next-themes": "^0.3.0",
    "node-appwrite": "^12.0.1",
    "prettier": "^3.2.5",
    "react": "^18",
    "react-datepicker": "^6.9.0",
    "react-dom": "^18",
    "react-dropzone": "^14.2.3",
    "react-hook-form": "^7.54.2",
    "react-phone-number-input": "^3.4.1",
    "tailwind-merge": "^2.6.0",
    "tailwindcss-animate": "^1.0.7",
    "twilio": "^5.0.4",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-datepicker": "^6.2.0",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.3",
    "eslint-plugin-import": "^2.29.1",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}
```

### app/page.tsx
```tsx
import Image from 'next/image';
import PatientForm from '@/components/forms/PatientForm';
import Link from 'next/link';

export default function Home() {
  return (
    <div className="flex h-screen max-h-screen">
      {/* TODO OTP Verification | PasskeyModal */}
      <section className="remove-scrollbar container my-auto">
        <div className="sub-container max-w-[496px]">
          <Image
            src="/assets/icons/logo-full.svg"
            height={1000}
            width={1000}
            alt="patient"
            className="mb-12 h-10 w-fit"
          />

          <PatientForm />

          <div className="text-14-regular mt-20 flex justify-between">
            <p className="justify-items-end text-dark-600 xl:text-left">
              © 2025 CarePulse
            </p>
            <Link href="/?admin=true" className="text-green-500">
              Admin
            </Link>
          </div>
        </div>
      </section>

      <Image
        src="/assets/images/onboarding-img.png"
        height={1000}
        width={1000}
        alt="patient"
        className="side-img max-w-[50%]"
      />
    </div>
  );
}
```

### components/forms/PatientForm.tsx
```tsx
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import SubmitButton from '../SubmitButton';
import { Form } from '@/components/ui/form';
import { createUser } from '@/lib/actions/patient.actions';
import CustomFormField from '../CustomFormField';
import { UserFormValidation } from '../../lib/validation';
import { useRouter } from 'next/navigation';

export enum FormFieldType {
  INPUT = 'input',
  TEXTAREA = 'textarea',
  PHONE_INPUT = 'phoneInput',
  CHECKBOX = 'checkbox',
  DATE_PICKER = 'datePicker',
  SELECT = 'select',
  SKELETON = 'skeleton',
}

const PatientForm = () => {
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);

  const form = useForm<z.infer<typeof UserFormValidation>>({
    resolver: zodResolver(UserFormValidation),
    defaultValues: {
      name: '',
      email: '',
      phone: '',
    },
  });

  async function onSubmit({
    name,
    email,
    phone,
  }: z.infer<typeof UserFormValidation>) {
    setIsLoading(true);
    try {
      const userData = { name, email, phone };
      const newUser = await createUser(userData);
      if (newUser) {
        router.push(`/patients/${newUser.$id}/register`);
      }
    } catch (error) {
      console.log(error);
    }
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 flex-1">
        <section className="mb-12 space-y-4">
          <h1 className="header">Hi there 👋</h1>
          <p className="text-dark-700">Schedule your first appointment.</p>
        </section>

        <CustomFormField
          fieldType={FormFieldType.INPUT}
          control={form.control}
          name="name"
          label="Full name"
          placeholder="John Doe"
          iconSrc="/assets/icons/user.svg"
          iconAlt="user"
        />

        <CustomFormField
          fieldType={FormFieldType.INPUT}
          control={form.control}
          name="email"
          label="Email"
          placeholder="johndoe@jsmastery.pro"
          iconSrc="/assets/icons/email.svg"
          iconAlt="email"
        />

        <CustomFormField
          fieldType={FormFieldType.PHONE_INPUT}
          control={form.control}
          name="phone"
          label="Phone number"
          placeholder="(555) 123-5678"
          iconSrc="/assets/icons/email.svg"
          iconAlt="email"
        />

        <SubmitButton isLoading={isLoading}>Get Started</SubmitButton>
      </form>
    </Form>
  );
};

export default PatientForm;
```

### components/CustomFormField.tsx
```tsx
'use client';

import { E164Number } from 'libphonenumber-js/core';
import {
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Control } from 'react-hook-form';
import { FormFieldType } from './forms/PatientForm';
import Image from 'next/image';
import 'react-phone-number-input/style.css';
import PhoneInput from 'react-phone-number-input';

interface CustomProps {
  control: Control<any>;
  fieldType: FormFieldType;
  name: string;
  label?: string;
  placeholder?: string;
  iconSrc?: string;
  iconAlt?: string;
  disabled?: boolean;
  dateFormat?: string;
  showTimeSelect?: boolean;
  children?: React.ReactNode;
  renderSkeleton?: (field: any) => React.ReactNode;
}

const RenderField = ({ field, props }: { field: any; props: CustomProps }) => {
  const { fieldType, iconSrc, iconAlt, placeholder } = props;

  switch (fieldType) {
    case FormFieldType.INPUT:
      return (
        <div className="flex rounded-md border border-dark-500 bg-dark-400">
          {iconSrc && (
            <Image
              src={iconSrc}
              height={24}
              width={24}
              alt={iconAlt || 'icon'}
              className="ml-2"
            />
          )}
          <FormControl>
            <Input
              placeholder={placeholder}
              {...field}
              className="shad-input border-0"
            />
          </FormControl>
        </div>
      );
    case FormFieldType.PHONE_INPUT:
      return (
        <FormControl>
          <PhoneInput
            defaultCountry="US"
            placeholder={placeholder}
            international
            withcountrycallingcode="true"
            value={field.value as E164Number | undefined}
            onChange={field.onChange}
            className="input-phone"
          />
        </FormControl>
      );
  }
};

const CustomFormField = (props: CustomProps) => {
  const { control, fieldType, name, label } = props;

  return (
    <FormField
      control={control}
      name={name}
      render={({ field }) => (
        <FormItem className="flex-1">
          {fieldType !== FormFieldType.CHECKBOX && label && (
            <FormLabel>{label}</FormLabel>
          )}

          <RenderField field={field} props={props} />

          <FormMessage className="shad-error" />
        </FormItem>
      )}
    />
  );
};

export default CustomFormField;
```

### lib/actions/patient.actions.ts
```tsx
import { ID, Query } from 'node-appwrite';
import { users } from '../appwrite.config';
import { parseStringify } from '../utils';

export const createUser = async (user: CreateUserParams) => {
  try {
    const newUser = await users.create(
      ID.unique(),
      user.email,
      user.phone,
      undefined,
      user.name
    );
    console.log({ newUser });

    return parseStringify(newUser);
  } catch (error: any) {
    if (error && error?.code === 409) {
      const documents = await users.list([Query.equal('email', [user.email])]);

      return documents?.users[0];
    }
  }
};
```

## 🧷 情報収集 Part1
かなりググり、今回の事象に一番近しいと思える`issues`を見つけました。
だが、`fix`されていない…
https://github.com/opennextjs/opennextjs-cloudflare/issues/473

### エラー文
![スクリーンショット 2025-03-30 23.35.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3427082/503b7524-9013-46db-9a10-09a4d1ec63c2.png)

とりあえず、エラー文右上の以下の通り、`Next.js`バージョンが古いようなので最新へ上げよっと！
```bash
Next.js (14.2.26) is outdated
```

### バージョン更新
```diff_javascript
- "next": "^14.2.26",
+ "next": "^15.2.4",
```

## 🧷 情報収集 Part2
`Next.js`バージョンアップ後に再起動した所、初めよりエラー発生箇所がだいぶ分かりやすく示されるようになりました。(もっと早めに上げとけばよかった..)

### エラー文
![スクリーンショット 2025-03-30 23.54.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3427082/f77a30d2-34a5-45be-bca5-423f9c38c67a.png)
![スクリーンショット 2025-03-30 23.54.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3427082/661b6600-6cf3-4cb0-99e1-e850e1a7336c.png)

当然だが、Next.jsのバージョンを上げたぐらいで進展するわけもなく、、ただ、PatientFormコンポーネント内で起こっているのは間違いなさそうだ！


あと、一応AIにも聞いてみた。
### エラー原因に関する NoteBookLM での回答
```
ご質問ありがとうございます。
「TypeError: Cannot read properties of undefined (reading 'split')」の根本的な原因が特定されたか、複数の要因が考えられるかについてですね。
提供された情報源に基づくと、このエラーの根本的な原因は完全に特定されているとは言えません。しかし、複数の情報源から、考えられる要因がいくつか示唆されています。

まず、GitHubのIssue #473 () では、まさにこの「TypeError: Cannot read properties of undefined (reading 'split')」というエラーが報告されています。
このIssueの投稿者によると、チュートリアルの手順に従った後、サイトのどのページにアクセスしてもこのエラーが発生するとのことです。
興味深いことに、yarn dev では問題がなく、yarn preview または yarn deploy を実行した際にのみ発生するようです。
また、別のエラーとして「TypeError: Cannot read properties of undefined (reading 'run')」が発生する場合もあると報告されています。投稿者は、コードがコンパイル済みであるため、エラーの発生源を追跡することが難しいと述べています。

このIssueからは、エラーが特定の環境（プレビューやデプロイ）でのみ発生すること、そして類似のエラーも存在することから、環境固有の問題や、ビルド・デプロイプロセスにおける何らかの不具合が原因として考えられます。投稿者は .next と .node-modules の削除を試したとも述べています。
さらに、投稿されたNext.jsのバージョンが 14.2.25 であり、最新バージョン（15.2.3時点）よりも古いことが指摘されており、古いバージョンのNext.jsに起因する可能性も示唆されています。
以前に opennext/sst を使用してAWSにデプロイしていたことが、何らかの競合を引き起こしている可能性も示唆されています。

一方、「テキストを貼り付けました」というソース () では、CustomFormField コンポーネントの内部実装にこのエラーの原因があるのではないかという疑問が提示されています。
このコンポーネントは、電話番号入力などのカスタムフォームフィールドを扱っており、内部で文字列操作 (split を直接使用しているかは不明ですが、ライブラリの内部処理で使用されている可能性はあります) を行っているかもしれません。

したがって、特定のコンポーネントの実装におけるデータ処理の不具合も原因の一つとして考えられます。
特に、電話番号のフォーマットや入力処理に関連するライブラリ (libphonenumber-js や react-phone-number-input) の使用方法に問題がある可能性も考えられます。


まとめると、現時点では「TypeError: Cannot read properties of undefined (reading 'split')」の根本的な原因は一つに特定されておらず、以下の複数の要因が複合的に関与している可能性があります。

　・環境固有の問題: yarn preview や yarn deploy でのみ発生することから、これらの環境における設定や処理に問題がある可能性があります。
　・ビルド・デプロイプロセスの不具合: コードがコンパイル済みであるために追跡が難しいことから、ビルドやデプロイの過程で何らかの問題が発生している可能性があります。
　・Next.jsのバージョン: 使用しているNext.jsのバージョンが古いことが指摘されており、これが原因である可能性があります。
　・過去のデプロイ環境との競合: 以前に使用していた opennext/sst の設定などが影響を与えている可能性があります。
　・特定のコンポーネントの実装: CustomFormField コンポーネント内部のデータ処理、特に電話番号入力に関連する処理に問題がある可能性があります。
 
根本的な原因を特定するためには、GitHubのIssueでの議論の進展や、CustomFormField コンポーネントの詳細な調査が必要となるでしょう。
```

## 🧷 解析
若干煮詰まってきたので、マジックカード！ゼロベース思考発動！！！
 * カード効果：これまでの試行錯誤、調査内容を一旦全てダンプし原点回帰する。

そしてここでふと思った。

示されたエラー箇所はたしかに自分が実装した`PatientForm`コンポーネント内だが、その下の以下にも着目した。
```bash
./node_modules/undici/lib/core/util.js
.next/static/chunks/app/page.js (2468:1)
```

## 🧷 仮説
### node_modules内がフォーカスされているということは、自分の実装ではなくインストールしたpackageの内部処理またはバージョンに問題があるのではないか？
ということで、この後、以下推測しつつ進めていき...

　👇 AI回答によると、「`CustomFormField` コンポーネントの内部実装にこのエラーの原因があるのではないかという疑問が提示されています。このコンポーネントは、電話番号入力などのカスタムフォームフィールドを扱っており、内部で文字列操作 (`split` を直接使用しているかは不明ですが、ライブラリの内部処理で使用されている可能性はあります」とのこと。
 
　👇 電話番号入力は、`PatientForm.tsx`の`onSubmit` > `createUser`
 　　 `createUser`は、`patient.actions.ts`で行なっている。
 
　👇 以下赤枠の`create`メソッドは`Appwrite`の以下`Users`サーバーの`Create userAPI`で行なっている。
 ![スクリーンショット 2025-03-31 0.16.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3427082/078ef36d-7a7c-467e-ab3c-a8f2ab21f18f.png)

　👇 インストール・利用中のAppwriteパッケージで関連しそうなのは、`node-appwrite` > [公式ページ](https://www.npmjs.com/package/node-appwrite)を見てみる。
 
　👇 「[This SDK is compatible with Appwrite server version 1.6.x. For older versions, please check previous releases.](https://www.npmjs.com/package/node-appwrite#:~:text=This%20SDK%20is%20compatible%20with%20Appwrite%20server%20version%201.6.x.%20For%20older%20versions%2C%20please%20check%20previous%20releases.)」…`node-appwrite`は、Appwriteサーバーバージョン：`1.6.x.`と互換性があるようだ。

## 🧷 検証
#### 今利用中の`node-appwrite`のバージョンは、`"^12.0.1”`だったので、試しに最新の`15.0.1`へ上げてみることに。

```diff_javascript
- "node-appwrite": "^12.0.1",
+ "node-appwrite": "^15.0.1",
```

まさかのエラーなくなりましたwww🎉
![スクリーンショット 2025-03-31 0.47.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3427082/c59a5f41-bba7-4247-9733-a7d5a0ed28b9.png)

**`node-appwrite`のバージョンが、Appwriteサーバーバージョンと互換性のないものを指定・利用していたこと**が起因だった可能性大です..

## 🎉 まとめ
エラー文で`node_modules`配下がスポットが当たっている時は、packageバージョンが的確かどうか確認・疑いの目を向けよう♪

### 修正内容
```diff_javascript
- "next": "^14.2.26",
+ "next": "^15.2.4",
```

```diff_javascript
- "node-appwrite": "^12.0.1",
+ "node-appwrite": "^15.0.1",
```

## 💫 参考文献
 * [オープンソースでセルフホスト可能なBaaSプラットフォーム「Appwrite」を使ってみた
](https://gigazine.net/news/20220328-appwrite/)
 * [[BUG] TypeError: Cannot read properties of undefined (reading 'split') #473](https://github.com/opennextjs/opennextjs-cloudflare/issues/473)
 * [Appwrite Node.js SDK
](https://www.npmjs.com/package/node-appwrite)