0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-03-31

はじめに

TypeScript・React学習コンテンツにて、ユーザー登録機能にAppwriteCreate userAPIを使用した所、以下TypeErrorになったためその調査過程を記しておきます。

TypeError: Cannot read properties of undefined (reading 'split')

スクリーンショット 2025-03-30 23.35.16.png

目次

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

🧷 開発環境

  • バックエンド・DB:Typescript、Appwrite
  • フロントエンド:HTML、TailwindCSS、React
  • フレームワーク:Next.js

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

package.json

{
  "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

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

'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

'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

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

とりあえず、エラー文右上の以下の通り、Next.jsバージョンが古いようなので最新へ上げよっと!

Next.js (14.2.26) is outdated

バージョン更新

- "next": "^14.2.26",
+ "next": "^15.2.4",

🧷 情報収集 Part2

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

エラー文

スクリーンショット 2025-03-30 23.54.05.png
スクリーンショット 2025-03-30 23.54.17.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コンポーネント内だが、その下の以下にも着目した。

./node_modules/undici/lib/core/util.js
.next/static/chunks/app/page.js (2468:1)

🧷 仮説

node_modules内がフォーカスされているということは、自分の実装ではなくインストールしたpackageの内部処理またはバージョンに問題があるのではないか?

ということで、この後、以下推測しつつ進めていき...

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

 👇 電話番号入力は、PatientForm.tsxonSubmit > createUser
   createUserは、patient.actions.tsで行なっている。

 👇 以下赤枠のcreateメソッドはAppwriteの以下UsersサーバーのCreate userAPIで行なっている。
スクリーンショット 2025-03-31 0.16.48.png

 👇 インストール・利用中のAppwriteパッケージで関連しそうなのは、node-appwrite > 公式ページを見てみる。

 👇 「This SDK is compatible with Appwrite server version 1.6.x. For older versions, please check previous releases.」…node-appwriteは、Appwriteサーバーバージョン:1.6.x.と互換性があるようだ。

🧷 検証

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

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

まさかのエラーなくなりましたwww🎉
スクリーンショット 2025-03-31 0.47.49.png

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

🎉 まとめ

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

修正内容

- "next": "^14.2.26",
+ "next": "^15.2.4",
- "node-appwrite": "^12.0.1",
+ "node-appwrite": "^15.0.1",

💫 参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?