react-router
react-router copied to clipboard
[Feature]: Validation of path and querystring
What is the new or updated feature that you are suggesting?
When using routing, complex verification work is often performed on the routing. Expect to add an API to the Route component to validate the route, and if the validation succeeds, render the Route's element
Like this:

If u accessing /users/1.html results as html;
If u accessing /users/a.html results as 404;
Why should this feature be included?
Validation of parameters in routing will make the program more secure and the parameters more rigorous,Changes to this requirement are also simple and can be implemented with a small amount of code.
In file packages/react-router/lib/components.tsx
Update interfaces:
export interface RouteProps {
caseSensitive?: boolean;
children?: React.ReactNode;
element?: React.ReactNode | null;
validator?: (path: Partial<Path>, params: PathMatch) => boolean;
index?: boolean;
path?: string;
}
export interface PathRouteProps {
caseSensitive?: boolean;
children?: React.ReactNode;
element?: React.ReactNode | null;
validator?: (path: Partial<Path>, params: PathMatch) => boolean;
index?: false;
path: string;
}
export interface LayoutRouteProps {
validator?: (path: Partial<Path>, params: PathMatch) => boolean;
children?: React.ReactNode;
element?: React.ReactNode | null;
}
export interface IndexRouteProps {
validator?: (path: Partial<Path>, params: PathMatch) => boolean;
element?: React.ReactNode | null;
index: true;
}
Update function createRoutesFromChildren
/**
* Creates a route config from a React "children" object, which is usually
* either a `<Route>` element or an array of them. Used internally by
* `<Routes>` to create a route config from its children.
*
* @see https://reactrouter.com/docs/en/v6/utils/create-routes-from-children
*/
export function createRoutesFromChildren(
children: React.ReactNode
): RouteObject[] {
let routes: RouteObject[] = [];
React.Children.forEach(children, (element) => {
if (!React.isValidElement(element)) {
// Ignore non-elements. This allows people to more easily inline
// conditionals in their route config.
return;
}
if (element.type === React.Fragment) {
// Transparently support React.Fragment and its children.
routes.push.apply(
routes,
createRoutesFromChildren(element.props.children)
);
return;
}
invariant(
element.type === Route,
`[${
typeof element.type === "string" ? element.type : element.type.name
}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`
);
let route: RouteObject = {
caseSensitive: element.props.caseSensitive,
element: element.props.element,
index: element.props.index,
validator: element.props.validator,
path: element.props.path,
};
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
In file packages/react-router/lib/router.ts
Update function matchRouteBranch
if (!match || (typeof meta.route.validator === "function" && !meta.route.validator(path, match))) return null;
function matchRouteBranch<ParamKey extends string = string>(
branch: RouteBranch,
pathname: string,
path: Partial<Path>
): RouteMatch<ParamKey>[] | null {
let { routesMeta } = branch;
let matchedParams = {};
let matchedPathname = "/";
let matches: RouteMatch[] = [];
for (let i = 0; i < routesMeta.length; ++i) {
let meta = routesMeta[i];
let end = i === routesMeta.length - 1;
let remainingPathname =
matchedPathname === "/"
? pathname
: pathname.slice(matchedPathname.length) || "/";
let match = matchPath(
{ path: meta.relativePath, caseSensitive: meta.caseSensitive, end },
remainingPathname
);
if (!match || (typeof meta.route.validator === "function" && !meta.route.validator(path, match))) return null;
Object.assign(matchedParams, match.params);
let route = meta.route;
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
pathnameBase: normalizePathname(
joinPaths([matchedPathname, match.pathnameBase])
),
route,
});
if (match.pathnameBase !== "/") {
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
}
}
return matches;
}
I was just thinking about similar technique as I've been migrating things to v6. Resolves some issues around dynamic routes being removed that I had. @ranyunlong could you open a PR since you already provided the code changes? You'll prob get better feedback there vs as only an issue.
You can validate params and redirect or error out in a loader in 6.4+ if that helps your use case.
If this is still a requested feature, please open a Proposal over in the Discussions tab so it can go through our new Open Development process. Thanks!