I have a AuthProvider
at in my application that will automatically redirect to the login page if the user is not logged in
interface IAuthContext {
token: string | undefined;
}
export const AuthContext = createContext<IAuthContext>({ token: undefined });
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [token, setToken] = useState<string | undefined>();
const navigate = useNavigate();
useEffect(() => {
const authToken = localStorage.getItem('authToken');
if (!authToken) {
navigate('/login');
}
setToken(token);
}, []);
return <AuthContext.Provider value={{ token }}>{children}</AuthContext.Provider>;
};
MyComp
is a child of AuthProvider
. Therefore, token
should always be defined, because if it doesn't exist, then AuthProvider
will redirect to the login page and MyComp
will never be rendered.
export const MyComp = () => {
const { token } = useContext(AuthContext);
if (!token) {
throw new Error('missing token');
}
const data = useMemo(() => fetchData(token), token);
return <div>{data}</div>;
};
It's annoying having assert that token is not undefined every time I need to use it. I have to type token
as string | undefined
, because I need to pass a default value to createContext
Is there a way to better type this so I don't need to assert that the token is defined and to not have to give a default value to createContext
?
CodePudding user response:
interface IAuthContext {
token: string;
}
export const AuthContext = createContext<IAuthContext>({ token: '' });
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [token, setToken] = useState('');
const navigate = useNavigate();
useEffect(() => {
const authToken = localStorage.getItem('authToken');
if (!authToken) {
navigate('/login');
} else {
setToken(authToken);
}
}, [token]);
if (!token) return null;
return <AuthContext.Provider value={{ token }}>{children}</AuthContext.Provider>;
};
CodePudding user response:
Found a way using typescript function overloading and a custom react hook
interface IAuthContext {
token: string;
}
const AuthContext = createContext<IAuthContext | undefined>(undefined);
export function useAuthContext(acceptUndefined: true): IAuthContext | undefined;
export function useAuthContext(): IAuthContext;
export function useAuthContext(acceptUndefined?: true) {
const authContext = useContext(AuthContext);
if (!authContext && !acceptUndefined) {
throw 'The component is not a child of AuthProvider';
}
return authContext;
}
Then, I can use the useAuthContext
hook to get a defined context.
const MyComp = () => {
const { token } = useAuthContext();
const data = useMemo(() => fetchData(token), token);
return <div>{data}</div>;
};
I can also pass true
to useAuthContext
if I want to handle the default context value myself
const MyComp = () => {
const authContext = useAuthContext(true);
if (!authContext) {
throw 'AuthContext undefined';
}
const { token } = authContext;
// ... //
};