Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
8
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Organization

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

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!!😎

苦情受付中...✍️

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
8
Help us understand the problem. What are the problem?