Skip to content

Commit

Permalink
feat(router): Better autocomplete for <Set>s (#11769)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe authored Dec 13, 2024
1 parent 95afaf9 commit 9e6b769
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .changesets/11769.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- feat(router): Better autocomplete for `<Set>`s (#11769) by @Tobbe

There's a limit to 10 wrapper components. After that additional props will not be included. To work around this you can either make sure to put wrapper components that doesn't require any props last, or just split your wrappers between multiple `<Set>`s
75 changes: 50 additions & 25 deletions packages/router/src/Set.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import React from 'react'

import type { AvailableRoutes } from '@redwoodjs/router'

type SetProps<P> = (P extends React.FC ? React.ComponentProps<P> : unknown) & {
/**
* A react component that the children of the Set will be wrapped
* in (typically a Layout component)
*/
wrap?: P
type RegularSetProps = {
/**
*`Routes` nested in a `<Set>` with `private` specified require
* authentication. When a user is not authenticated and attempts to visit
Expand All @@ -23,6 +18,38 @@ type SetProps<P> = (P extends React.FC ? React.ComponentProps<P> : unknown) & {
* @deprecated Please use `<PrivateSet>` instead and specify this prop there
*/
unauthenticated?: keyof AvailableRoutes
}

/**
* A set containing public `<Route />`s
*/
export function Set<WrapperProps>(
props: CommonSetProps<WrapperProps> & RegularSetProps,
) {
// @MARK: Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block
return <>{props.children}</>
}

type CommonSetProps<P> = (P extends React.FC<any>
? React.ComponentProps<P>
: P extends React.FC<any>[]
? React.ComponentProps<P[0]> &
React.ComponentProps<P[1]> &
React.ComponentProps<P[2]> &
React.ComponentProps<P[3]> &
React.ComponentProps<P[4]> &
React.ComponentProps<P[5]> &
React.ComponentProps<P[6]> &
React.ComponentProps<P[7]> &
React.ComponentProps<P[8]> &
React.ComponentProps<P[9]>
: unknown) & {
/**
* A React component, or an array of React components, that the children of
* the Set will be wrapped in (typically a Layout component and/or a context)
*/
wrap?: P
/**
* Route is permitted when authenticated and user has any of the provided
* roles such as "admin" or ["admin", "editor"]
Expand All @@ -36,22 +63,13 @@ type SetProps<P> = (P extends React.FC ? React.ComponentProps<P> : unknown) & {
whileLoadingPage?: () => ReactElement | null
}

/**
* A set containing public `<Route />`s
*/
export function Set<WrapperProps>(props: SetProps<WrapperProps>) {
// @MARK: Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block
return <>{props.children}</>
}

type PrivateSetProps<P> = Omit<SetProps<P>, 'private' | 'unauthenticated'> & {
/** The page name where a user will be redirected when not authenticated */
unauthenticated: keyof AvailableRoutes
}

/** @deprecated Please use `<PrivateSet>` instead */
export function Private<WrapperProps>(props: PrivateSetProps<WrapperProps>) {
export function Private<WrapperProps>(
props: CommonSetProps<WrapperProps> & {
/** The page name where a user will be redirected when not authenticated */
unauthenticated: keyof AvailableRoutes
},
) {
// @MARK Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block
return <>{props.children}</>
Expand All @@ -60,15 +78,20 @@ export function Private<WrapperProps>(props: PrivateSetProps<WrapperProps>) {
/**
* A set containing private `<Route />`s that require authentication to access
*/
export function PrivateSet<WrapperProps>(props: PrivateSetProps<WrapperProps>) {
export function PrivateSet<WrapperProps>(
props: CommonSetProps<WrapperProps> & {
/** The page name where a user will be redirected when not authenticated */
unauthenticated: keyof AvailableRoutes
},
) {
// @MARK Virtual Component, this is actually never rendered
// See analyzeRoutes in utils.tsx, inside the isSetNode block
return <>{props.children}</>
}

export const isSetNode = (
node: ReactNode,
): node is ReactElement<SetProps<any>> => {
): node is ReactElement<CommonSetProps<any> & RegularSetProps> => {
return (
React.isValidElement(node) &&
(node.type === Set || node.type === PrivateSet || node.type === Private) &&
Expand All @@ -79,13 +102,15 @@ export const isSetNode = (

export const isPrivateSetNode = (
node: ReactNode,
): node is ReactElement<PrivateSetProps<unknown>> => {
): node is ReactElement<
CommonSetProps<unknown> & { unauthenticated: keyof AvailableRoutes }
> => {
return React.isValidElement(node) && node.type === PrivateSet
}

// Only identifies <Private> nodes, not <Set private> nodes
export const isPrivateNode = (
node: ReactNode,
): node is ReactElement<SetProps<any>> => {
): node is ReactElement<CommonSetProps<any> & RegularSetProps> => {
return React.isValidElement(node) && node.type === Private
}

0 comments on commit 9e6b769

Please sign in to comment.