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;
// ... //
};
