Reputation: 1769
I'm trying to create a useAuth custom hook within React 17 and Typescript. The solution that I have is kind of working, but it requires the following when accessing the hook methods:
const auth = useAuth();
// Do other stuff
const result = await auth?.login(data);
The issue is that I need to use the ?
operator when calling the login
method, as the auth
object can be null.
The reason for this I know is how I have implemented in the context, however I can't figure out how in TypeScript to create the context with a null context object by default (i.e. no currentUser).
This is how I've currently implemented the hook:
interface AuthProvider {
children: ReactNode;
}
interface ProvideAuthContext {
currentUser: AppUser | null,
login: (loginInfo: LoginInfo) => Promise<LoginResponse>,
register: (registerInfo: RegisterInfo) => Promise<RegisterResponse>
}
const authContext = createContext<ProvideAuthContext | null>(null);
export function ProvideAuth({ children }: AuthProvider) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>
}
// Hook for child components to get the auth object and re-render when it changes.
export const useAuth = () => {
return useContext(authContext);
};
// Provider hook that creates auth object and handles state
function useProvideAuth() {
// This is where I implement the ProvideAuthContext interface
// Return the user object and auth methods
return {
currentUser,
login,
register
};
}
Any help with this would be amazing. I'm 99% sure it's got to do with this line:
const authContext = createContext<ProvideAuthContext | null>(null);
.
I can't do it this way: const authContext = createContext<ProvideAuthContext | null>({ currentUser: null });
as I get the following error:
Argument of type '{ currentUser: null; }' is not assignable to parameter of type 'ProvideAuthContext'. Type '{ currentUser: null; }' is missing the following properties from type 'ProvideAuthContext': login, register
I can't change the ProvideAuthContext
login and register methods to be login?:... or register?:...
as I can't then use them in components that use the useAuth hook.
Thanks, Justin
Upvotes: 1
Views: 1741
Reputation: 103
I see that previous answers tell you to set an initial value other than null
in your context
, but I don't think it really makes sense to do that in your case. Initial values for context
shouldn't be used unless you really need this context
to have an initial value if used outside a provider, which doesn't fit your case because you're saying that your ProvideAuth
should always be wrapped around your app. Setting initial values can actually make it harder to debug later on because there's no way for you to know if the values that you got are actually from context or from your provider. Instead, I'd advise you to introduce an if
check in your hook to make sure that the context is not null, and in case it is, it throws an error:
const authContext = createContext<ProvideAuthContext | null>(null);
export const useAuth = () => {
const auth = useContext(authContext);
if (!auth) {
throw new Error('useAuth can\'t be used outside a ProvideAuth.')
}
return auth
};
This can make it easier to debug because if you use your useAuth
hook but you forgot to wrap your app around provider you immediately get an error message telling you exactly what's wrong, with the benefit that the auth
returned by this hook will never be null
and typescript won't complain anymore. I learned this from Kent C. Dodds' blog.
Upvotes: 1
Reputation: 38140
You can make use of the never
type which is inferred from throw new Error
:
interface ProvideAuthContext {
currentUser: AppUser | null,
login: (loginInfo: LoginInfo) => Promise<LoginResponse>,
register: (registerInfo: RegisterInfo) => Promise<RegisterResponse>
}
const authContext = createContext<ProvideAuthContext>({
currentUser: null,
login: () => { throw new Error("context is missing") },
register: () => { throw new Error("context is missing") }
});
Upvotes: 1