🚨

TypeScriptでuseContextを使う際に、createContextの型チェックにハマった件について

2021/03/12に公開

NextとTypeScriptで状態管理をするためにuseContextを使用しようとして、NetlifyのuseContextの導入の記事なんかを参考に導入を進めていたのですが、記事通りに実装したら以下のエラーが発生。

Error: Element type is invalid: expected a 
string (for built-in components) or 
a class/function (for composite components) but got: undefined. 
You likely forgot to export your component 
from the file it's defined in, or you might have mixed up default 
and named imports.

直訳すると、

エラーが発生しました。要素のタイプが無効です。
string(組み込みコンポーネントの場合)または 
class/function(複合コンポーネントの場合)を期待しましたが、「undefined」となりました。
コンポーネントを定義されたファイルからexportするのを忘れたか 
定義されているファイルからコンポーネントをexportするのを忘れたか、
デフォルトのimportと名前付きのimportを混同した可能性があります。

エラーが起きている箇所はcreateContextの部分です。

interface Props {
  children: ReactNode | ReactNode[];
}

const PostContext = React.createContext<Partial<Props>>({});

上記のようにcreateContextを使っていたのですが、これだと問題があります。
それは、createContextdefaultValueundifinedとなってしまう可能性があり、contextを使いたいComponentではundifinedかどうかを毎回チェックする必要が出てくるということです。ちなみにチェックせずにundifinedをそのまま渡してもどこかでエラーになってしまいます。参考(React公式)

解決策

さて、ここからはこの問題、つまりcreateContextのdefaultValueがundifinedのままuseContextで使用されてしまうことの解決策を説明します。

簡単にいうと

解決策を言葉で説明するなら、undifinedのdefaultValueがcontextで各コンポーネントに渡らないようにフィルタリングしてあげるということです。

実装しよう

今回は以下の記事を参考に実装しました。
React Context with TypeScript: Part 4 - Creating a context with no default and no undefined check
【React】デフォルト値もundefinedチェックもいらないcreateContext【Typescript】
React ContextのdefaultValueのちょっとかしこい(かもしれない)使い方

PostContext.tsx

import React, { useContext, useState } from "react";

type postContextType = {
  getCurrentPost: () => void;
};

function createCtx<ContextType>() {
  const ctx = React.createContext<ContextType | undefined>(undefined);
  const useCtx = () => {
    const c = React.useContext(ctx);
    if (!c) throw new Error("useCtx must be inside a Provider with a value");
    return c;
  };
  return [useCtx, ctx.Provider] as const;
}

export const [usePost, SetPostProvider] = createCtx<postContextType>();

export const PostProvider: React.FC = (props) => {
  const post = usePostCtx();
  return <SetPostProvider value={post}>{props.children}</SetPostProvider>;
};

export const usePostCtx: any = () => {
  const displayHello = () => {
    console.log("Hello!!");
  };
  return { getCurrentPost };
};

createCtxではReact.useContext(ctx)undifinedの場合はエラーメッセージを、そうでない場合はそのままcontextを返します。
そうすることで、useContextにundifineddefaultValueが渡った場合にはエラーとなるので、undifinedに気づくことができるというわけです。

今回は、確認のためにコンソールにHello!!と表示するdisplayHelloという関数を作ってみました。

画像の通り、Hello!!と表示されているので、エラーは治り、無事にcontextが使えるようになりました!!

Discussion