30
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【React】デフォルト値もundefinedチェックもいらないcreateContext【Typescript】

Posted at

Introduction

Typescriptによる型定義とContextによるState管理で、ReactのDX(Developer Experience)は劇的に進化しました。

しかし、素晴らしい型定義によって、謎に苦しめられることもあります。

Problem

createContextにはデフォルト値を渡す必要がありますが、往々にしてそのデフォルト値はundefinedです。

const AuthContext = React.createContext<authContextType | undefined>(undefined);

これの何が問題なのでしょうか?
それはContextを使用する際に致命的になります。

// どこかでuseContextして作られたuseAuth関数
const Auth = useAuth()!;

createContextのデフォルト値としてundefinedを指定したことで、Contextを使用する際にその値がundefinedでないかを”全ての場所で”逐一確認する必要があります。

根本的に、undefinedはアプリケーションにとって何の意味もなさない値です。
それを、わざわざ確認しなければいけないということは、バグやエラーの温床になります。

Solution

デフォルト値をundefinedとせず、未定義チェックを行えるcretaeContextのwrapperを作成して解決します。

function createCtx<ContextType>() {
  const ctx = React.createContext<ContextType | undefined>(undefined);
  function 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;
}

今回の問題は、デフォルト値undefinedのContextに対して、useContextをしてしまうとでundefinedがuseContextに紛れ込んでしまうことでした。

そこで、wrapper関数に未定義チェック関数を定義し、useContextundefinedである場合はエラーをthrowします。

これで、wrapper関数createCtxで定義されたuseCtxは、<ContextType>で渡されるジェネリック型を返します。
無事、useContextからundefinedを駆逐しました。

Example

ユーザー管理、ログイン、ログアウトなどを管理するContextのExampleを作ってみました。

type User = {
  id: string;
  name: string;
};

type authContextType = {
  user: User | null;
  signIn: () => void;
  signUp: () => void;
  signOut: () => void;
};

function createCtx<ContextType>() {
  const ctx = React.createContext<ContextType | undefined>(undefined);
  function 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;
}

const [useAuth, SetAuthProvider] = createCtx<authContextType>();

const AuthProvider: React.FC = (props) => {
  const auth = useAuthCtx();
  return <SetAuthProvider value={auth}>{props.children}</SetAuthProvider>;
};

const useAuthCtx = (): authContextType => {
  const [user, setUser] = React.useState<User | null>(null);
  const signIn = () => {
    // Some sign in action
  };
  const signUp = () => {
    // Some sign up action
  };
  const signOut = () => {
    // Some sign out action
  };

  return { user, signIn, signUp, signOut };
};

export const App = () => {
  const auth = useAuth();
  return (
    <AuthProvider>
      <button onClick={() => {auth.signIn()}}>ログイン</button>
      <button onClick={() => {auth.signUp()}}>サインアップ</button>
    </AuthProvider>
  );
};

Sample

【Qiita記事準備中】 React.createContextで管理するFirebase Authentication

Happy Hacking!!😎

苦情受付中...✍️

30
17
1

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
30
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?