I'm trying to implement a universal wrapper for pages passed to Route
that is limiting access to the page based on current user permissions. This component means to be universal because it's basically doing the same thing: if a user is allowed to go to this page - the page is shown, if not - the not found view is shown.
Implementation
const RouteCan = ({ action, subject }: RouteCan) => {
return (
<>
<Can I={action} as={subject}>
<Outlet />
</Can>
<Can not I={action} as={subject}>
<NotFoundView />
</Can>
</>
)
// Or ...
if (can(action, subject)) return <Outlet />
return <NotFoundView />
}
usage
<Route
path="/secure-path"
element={<RouteCan I="view" a="secure-info" />}
>
/* inner routes */
</Route>
The thing is none of <Can />
or can
can accept as arguments just all possible pair variants as my application Ability type is defined as a union of only possible action-subject pairs:
type Ability =
| ['manage' | 'view', 'secure-path']
| ['manage' | 'view' | 'edit', 'public-path']
// This causing an error
type RouteCanProps = {
I: Ability[0]
as: Ability[1]
}
Is there any opportunity to create a type that provides only possible tuples of values, not just union of all possible actions and subjects separately?
Link on codesandbox - Everything is working as expected, the problem is only on type of RouteCan
props.
We need to use distributive conditional types, which can be achieved with the following type:
type MappedAbility<T extends Ability = Ability> = T extends T
? {
I: T[0];
as: T[1];
}
: never;
The part that allows us to distribute is T extends T
, after that the following code is executed for each member of the union separately.
Usage:
// type Result = {
// I: "manage" | "view";
// as: "secure-path";
// } | {
// I: "manage" | "view" | "edit";
// as: "public-path";
// }
type Result = MappedAbility;