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関数に未定義チェック関数を定義し、useContext
がundefined
である場合はエラーを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!!😎
苦情受付中...✍️