かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript VSCode で Expression produces a union type that is too complex to represent エラーになる

途中参加した Next.js (TypeScript) + chakra-ui を使ったプロジェクトを VS Code で開いたらコンポーネントが全面的に 複雑すぎる型 Expression produces a union type that is too complex to represent. エラーになっていました
Expression produces a union type that is too complex to represent.
なんもわからん…

VSCode の TypeScript とプロジェクトで使用されている TypeScript のバージョンが異なるのが原因

1. ワークスペースで使用する TypeScript を変更する

  1. VS Code⌘ + Shift + P でコマンドパレットを開く
  2. TypeScript: Select TypeScript version command を選択
  3. プロジェクトで使用している TypeScript を選択する

TypeScript: Select TypeScript version command

今回は Use Workspace Version の v4.9.5 を選択すればOKでした

2. 自動的に Workspace の TypeScript を使用するようにする

今回と同様の問題が発生するたびに毎回上記を手動で行うのは面倒です。
VS Code にはどの TypeScript を使用するかの設定があるるので指定しておけば面倒にならなそうです!

.vscode/settings.json に下記の設定を追加する

{
  "typescript.enablePromptUseWorkspaceTsdk": true
}

typescript.enablePromptUseWorkspaceTsdktrue ならプロジェクト (Workspace) の Lint を使い、false なら VS Code の Lint を使用する設定

この設定があり VS Code と プロジェクト の TypeScript のバージョンが異なる場合 Workspace の TypeScript を使用するか聞かれるので Allow を選択すれば OK!

複数人で開発する TypeScript のプロジェクトでは setting.json"typescript.enablePromptUseWorkspaceTsdk": true の設定を含めておけば幸せになれそうです!

おわり ₍ ᐢ. ̫ .ᐢ ₎


[参考]

JavaScript Fetch でステータスコードを使ってエラーハンドリングしたい

Next.js の api を使って非同期処理のモックを作っていて Fetch API だとステータスコードが 200 系以外でもエラーが発生しないみたいだったのでステータスコードでエラーハンドリングできるようにしたメモ

環境
  • node.js v18.14.0
  • Next.js 13.2.4
    • React 18.2.0
  • TypeScript 5.0.2

API (Next.js)

Next.js でシンプルな API を作成

  1. POST 以外の場合は 405 (Method Not Allowed) を返す
  2. 認証が通らなかったら 400 (Bad Request) を返す
  3. 認証が通ったら 200 で {"success": "true"}json を返す

/src/api/auth.ts

import { NextApiRequest, NextApiResponse } from 'next';

type Data = {
  success: boolean;
  message?: string;
};

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>,
) {
  if (req.method !== 'POST') {
    return res.status(405).send({ message: 'Bad Request!', success: false });
  }

  const { user, password } = req.body;
  if ( !login({user, password}) ) {
    return res.status(400).send({ message: 'Bad Request!', success: false });
  }

  return res.status(200).json({ success: true });
}

フロントからの Fetch

const postLoginForm = async ({ user, password }) => {
  const method = 'POST';
  const headers = {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  };
  const body = JSON.stringify({ user, password });

  try {
    const res = await fetch('/api/auth', { method, headers, body });
    const { success } = await res.json();
    
    return !!success;
  } catch (error) {
    if (error instanceof Error) {
      console.log('Error', error.message);
    } else {
      console.log('Error');
    }
    // エラー時の処理
  }

  return false;
}

上記のコードは API からステータスコードが 405 の時もログインに失敗して 400 が返ってきた場合も処理が cactch 節に流れません

Response.ok を使ってステータスコードが 200 番台以外は例外を発生させるようにする

Fetch API は通信そのものがエラーにならない限りステータスコードが 200番台以外でも例外を発生させないようなので、try - catch を使ったエラーハンドリングをするには意図的に例外を発生させる必要がありました

Response.ok (読取専用)
レスポンスが成功 (200–299 の範囲のステータス) したか否かを通知する論理値が入ります。
cf. Response.ok - Web API | MDN

Fetch API の返すレスポンスオブジェクトの Response.okfalse なら例外を発生させれば OK

const postLoginForm = async ({ user, password }) => {
  // 略

  try {
    const res = await fetch('/api/auth', { method, headers, body })
+     .then((res) => {
+       if ( !res.ok ) {
+         throw new Error(res.statusText);
+       }
+       return res;
+     });
    const { success } = await res.json();
    
    return !!success;
  } catch (error) {
    if (error instanceof Error) {
      console.log('Error', error.message);
    } else {
      console.log('Error');
    }
    // エラー時の処理
  }

  return false;
}

レスポンスのステータスが 200 番台以外だと例外にできるようになりました
このとき error.message には Response.statusText が入っています。これは API から返されるデータの message ではなくBad Request とか Internal Server Error のようなステータスコードに対応したテキストになります。
API から返されるメッセージを error.message にしたい場合は Response から json を取り出して意図的に throw する必要があります

カスタムメッセージを使う場合

const postLoginForm = async ({ user, password }) => {
  // 略

  try {
-   const res = await fetch('/api/auth', { method, headers, body })
-     .then((res) => {
-       if ( !res.ok ) {
-         throw new Error(res.statusText);
-       }
-       return res;
-     });
-   const { success } = await res.json();
+   const data = await fetch('/api/auth', { method, headers, body })
+     .then( async(res) => {
+       const data = await res.json(); 
+       if ( !res.ok ) {
+         throw new Error(data.message);
+       }
+       return data;
+     });
+   const { success } = data;   
  
    return !!success;
  } catch (error) {
    if (error instanceof Error) {
      console.log('Error', error.message);
    } else {
      console.log('Error');
    }
    // エラー時の処理
  }

  return false;
}

ステータスコードに関わらず response.json()API から渡されたデータを取り出しその中のメッセージを threw すれば OK

所管

axios や ky を使うことが多く fetch をガッツリ使ってこなかったのでステータスコードが 200 以外でもエラーとして扱われないのは予想外だったので Response.okResponse.statusText など理解が深まってよかったです!

おわり₍ ᐢ. ̫ .ᐢ ₎


[参考]

Next.js v13 × Chakra UI NextLink を使うTips

Next.js の Link に Chakra UI の Link や Button のデザインを適応させる方法の Tips

環境
  • Next.js 13.2.4
    • Reat 18.2.0
  • @chakra-ui/react 2.5.3
  • TypeScript 5.0.2

cf. Getting Started with Next.js - Chakra UI

$ npm i @chakra-ui/next-js

Link

import { Link } from '@chakra-ui/next-js';

export default Page(): JSX.Element {
  return (
    <Link href="/page">Link Text</Link>
  );
}

Chakra UI の as に NextLink を渡せばOK

import NextLink from 'next/link';
import { Button } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
    <Button as={NextLink}>Button Label</Button>
  );
}

Next.js では SPA的なページ遷移をするには Next が用意している Link コンポーネントを使う必要があります
Next の Link コンポーネント<a> タグを出力するので同様に <a> タグや <button> タグを出力するコンポーネントを使用する時は passHref を使うよう書かれています

If the child is a custom component that wraps an <a> tag - Next.js

If the child of Link is a custom component that wraps an <a> tag, you must add passHref to Link. This is necessary if you’re using libraries like styled-components. Without this, the <a> tag will not have the href attribute, which hurts your site's accessibility and might affect SEO. If you're using ESLint, there is a built-in rule next/link-passhref to ensure correct usage of passHref.

import Link from 'next/link'
import styled from 'styled-components'

// This creates a custom component that wraps an <a> tag
const RedLink = styled.a`
  color: red;
`

function NavLink({ href, name }) {
  return (
    <Link href={href} passHref legacyBehavior>
      <RedLink>{name}</RedLink>
    </Link>
  )
}

export default NavLink
If the child is a functional component

If the child of Link is a functional component, in addition to using passHref and legacyBehavior, you must wrap the component in React.forwardRef:

import Link from 'next/link'

// `onClick`, `href`, and `ref` need to be passed to the DOM element
// for proper handling
const MyButton = React.forwardRef(({ onClick, href }, ref) => {
  return (
    <a href={href} onClick={onClick} ref={ref}>
      Click Me
    </a>
  )
})

function Home() {
  return (
    <Link href="/about" passHref legacyBehavior>
      <MyButton />
    </Link>
  )
}

export default Home

cf. next/link | Next.js

Next.js v13 では Chakra UI のコンポーネントを使った時に passHref入れ子にすると Hydration failed エラーが発生する問題

Next.js v13 ではドキュメントの例に習い Chakra UI の <Link>, <Button> コンポーネントを使おうとすると、リロード時に <a> タグが入れ子になっているという理由で Hydration failed エラーが発生してしまうようです

import NextLink from 'next/link';
import { Link } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
    <NextLink href='/page' passHref>
      <Link>Link Text</Link>
    </NextLink>
  );
}

=> Error: Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching <a> in <a>.

Button も as="a" で a タグ扱いにすると同様のエラーが発生する

Button のデザインを使いたいリンクを作成したい時にありがちなパターンも passHref入れ子にすると Hydration failed エラーが発生する

import NextLink from 'next/link';
import { Button } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
    <NextLink href='/page' passHref>
      <Button as="a">Link Text</Button>
    </NextLink>
  );
}

=> Error: Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching <a> in <a>.

Next.js v13 で Chakra UI の Link, Buttton を使う方法

cf. Getting Started with Next.js - Chakra UI

$ npm i @chakra-ui/next-js
- import NextLink from 'next/link';
- import { Link } from '@chakra-ui/react';
+ import { Link } from '@chakra-ui/next-js'

export default Page(): JSX.Element {
  return (
-   <NextLink href='/page' passHref>
-     <Link>Link Text</Link>
-   </NextLink>
+   <Link href="/page">Link Text</Link>
  );
}

passHref を使うのではなく Chakra UI の as に NextLink を指定すれば NextLink な ボタンになる

import NextLink from 'next/link';
import { Button } from '@chakra-ui/react';

export default Page(): JSX.Element {
  return (
-   <NextLink href='/page' passHref>
-     <Button as="a">Link Text</Button>
-   </NextLink>
+   <Button as={NextLink}>Link Text</Button>
  );
}

所感

Next.js と Chakra UI を使ってサクッとサンプルを作りたかっただけなのですが、思いもしない箇所でハマってしまいました。
Hydration failed のエラーについては理解が浅かったのでまた別途調べておきたいと思いました。

おわり


[参考]