'Assert that value from Context is not null in child component?
I'm building a Next.js/React app with TypeScript. I have a UserContext in pages/_app.js which provides a user: User | null object to all child components.
I also have a ProtectedRoute component to wrap pages which shouldn't be accessible without being logged in (it consumes the user from UserContext and redirects if user === null).
However, the type of user that children of ProtectedRoute see is still User | null. It should just be User, since we can't access a protected route without being logged in. Is there a TypeScript or React design pattern that would accomplish this?
I looked at non-null assertions with !, but those are default-disabled by ESLint and I don't want to do that in every child of ProtectedRoute. I also looked at type guards, but not sure if there's a way to avoid doing that in every child component.
Ideally, the type of user from UserContext would change to just User instead of User | null if the component consuming the context is a child of ProtectedRoute.
Thank you!
EDIT: ProtectedRoute code:
function ProtectedRoute({ children }: { children: JSX.Element }) {
const { user } = useSupabase(); // This has type `User | null`
const router = useRouter();
useEffect(() => {
if (!user) {
router.push("/login"); // There may be a more Next-specific way to do this...
}
}, [user, router]);
return user ? children : null;
}
Solution 1:[1]
If you're positive that the component will never be rendered in a logged-out state, then you can use an assertion function in a custom hook to transform the type.
Note that you're making a guarantee by throwing an error if the condition can't be met, and that this will crash your component if you use it incorrectly.
// You didn't show what these are or where they come from:
interface User {}
declare function useSupabase (): { user: User | null };
declare function useRouter (): string[];
type NonNullableProperties <T, Key extends keyof T> = {
[K in keyof T]: K extends Key ? NonNullable<T[K]> : T[K];
};
function assert (expr: unknown, msg?: string): asserts expr {
if (!expr) throw new Error(msg ?? 'Value is falsy');
}
function useSupabaseAuthenticated (): NonNullableProperties<ReturnType<typeof useSupabase>, 'user'> {
const result = useSupabase();
assert(result.user, 'Invalid state');
return result as NonNullableProperties<typeof result, 'user'>;
}
function ProtectedRoute ({ children }: { children: JSX.Element }) {
const { user } = useSupabaseAuthenticated(); // is now just User
// const router = useRouter();
// useEffect(() => {
// if (!user) router.push("/login");
// }, [user, router]);
// return user ? children : null;
return children;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
