react-router params can only have a string value (#1) (#28868)

* react-router params can only have a string value

* Update index.d.ts

* Use mapped types and make the type optional

* Fix tests

* trailing whitespace
This commit is contained in:
John Reilly
2018-09-13 22:35:11 +01:00
committed by GitHub
parent 972309a84a
commit c2e4b83961
18 changed files with 60 additions and 40 deletions

View File

@@ -51,8 +51,8 @@ export interface RedirectProps {
strict?: boolean;
}
export interface RouteComponentProps<P> {
match: match<P>;
export interface RouteComponentProps<Params extends { [K in keyof Params]?: string }> {
match: match<Params>;
location: H.Location;
history: H.History;
staticContext?: any;

View File

@@ -1,6 +1,7 @@
// Type definitions for react-router-config 1.0
// Project: https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config
// Definitions by: François Nguyen <https://github.com/lith-light-g>
// John Reilly <https://github.com/johnnyreilly>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
@@ -8,7 +9,7 @@ import * as React from "react";
import { RouteComponentProps, SwitchProps, match } from "react-router";
import { Location } from "history";
export interface RouteConfigComponentProps<T> extends RouteComponentProps<T> {
export interface RouteConfigComponentProps<Params extends { [K in keyof Params]?: string } = {}> extends RouteComponentProps<Params> {
route?: RouteConfig;
}
@@ -21,12 +22,12 @@ export interface RouteConfig {
routes?: RouteConfig[];
}
export interface MatchedRoute<T> {
export interface MatchedRoute<Params extends { [K in keyof Params]?: string }> {
route: RouteConfig;
match: match<T>;
match: match<Params>;
}
export function matchRoutes<T>(routes: RouteConfig[], pathname: string): Array<MatchedRoute<T>>;
export function matchRoutes<Params extends { [K in keyof Params]?: string }>(routes: RouteConfig[], pathname: string): Array<MatchedRoute<Params>>;
export function renderRoutes(
routes: RouteConfig[] | undefined,

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import { RouteConfig, matchRoutes, MatchedRoute, renderRoutes, RouteConfigComponentProps } from "react-router-config";
import { BrowserRouter } from "react-router-dom";
const Root = ({ route }: RouteConfigComponentProps<void>) => (
const Root = ({ route }: RouteConfigComponentProps) => (
<div>
<h1>Root</h1>
{/* child routes won't render without this */}
@@ -10,13 +10,13 @@ const Root = ({ route }: RouteConfigComponentProps<void>) => (
</div>
);
const Home = ({ route }: RouteConfigComponentProps<void>) => (
const Home = ({ route }: RouteConfigComponentProps) => (
<div>
<h2>Home</h2>
</div>
);
const Child = ({ route }: RouteConfigComponentProps<void>) => (
const Child = ({ route }: RouteConfigComponentProps<{ id: string }>) => (
<div>
<h2>Child</h2>
{/* child routes won't render without this */}

View File

@@ -3,6 +3,7 @@
// Definitions by: Tanguy Krotoff <https://github.com/tkrotoff>
// Huy Nguyen <https://github.com/huy-nguyen>
// Philip Jackson <https://github.com/p-jackson>
// John Reilly <https://github.com/johnnyreilly>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
@@ -55,7 +56,7 @@ export interface NavLinkProps extends LinkProps {
activeStyle?: React.CSSProperties;
exact?: boolean;
strict?: boolean;
isActive?<P>(match: match<P>, location: H.Location): boolean;
isActive?<Params extends { [K in keyof Params]?: string }>(match: match<Params>, location: H.Location): boolean;
location?: H.Location;
}
export class NavLink extends React.Component<NavLinkProps, any> {}

View File

@@ -3,11 +3,12 @@ import {
NavLink,
NavLinkProps,
match,
Link
Link,
RouteComponentProps
} from 'react-router-dom';
import * as H from 'history';
const getIsActive = (extraProp: string) => (match: match<any>, location: H.Location) => !!extraProp;
const getIsActive = (extraProp: string) => (match: match, location: H.Location) => !!extraProp;
interface Props extends NavLinkProps {
extraProp: string;
@@ -21,6 +22,17 @@ export default function(props: Props) {
);
}
type OtherProps = RouteComponentProps<{
id: string;
}>;
const Component: React.SFC<OtherProps> = props => {
const { id } = props.match.params;
return (
<div>{id}</div>
);
};
<Link to="/url" />;
const acceptRef = (node: HTMLAnchorElement | null) => {};

View File

@@ -1,6 +1,7 @@
// Type definitions for react-router-navigation-core 1.0
// Project: https://github.com/LeoLeBras/react-router-navigation#readme
// Definitions by: Kalle Ott <https://github.com/kaoDev>
// John Reilly <https://github.com/johnnyreilly>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8

View File

@@ -1,6 +1,7 @@
// Type definitions for react-router-navigation 1.0
// Project: https://github.com/LeoLeBras/react-router-navigation#readme
// Definitions by: Kalle Ott <https://github.com/kaoDev>
// John Reilly <https://github.com/johnnyreilly>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8

View File

@@ -80,4 +80,4 @@ export interface LocationChangeAction {
export function routerMiddleware(history: History): Middleware;
export function createMatchSelector(path: string): (state: { router: RouterState }) => match<{}> | null;
export function createMatchSelector(path: string): (state: { router: RouterState }) => match | null;

View File

@@ -26,12 +26,12 @@ import * as H from 'history';
// This is the type of the context object that will be passed down to all children of
// a `Router` component:
export interface RouterChildContext<P> {
export interface RouterChildContext<Params extends { [K in keyof Params]?: string } = {}> {
router: {
history: H.History
route: {
location: H.Location
match: match<P>
match: match<Params>
}
};
}
@@ -64,10 +64,10 @@ export interface StaticContext {
statusCode?: number;
}
export interface RouteComponentProps<P, C extends StaticContext = StaticContext, S = H.LocationState> {
export interface RouteComponentProps<Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState> {
history: H.History;
location: H.Location<S>;
match: match<P>;
match: match<Params>;
staticContext?: C;
}
@@ -106,8 +106,8 @@ export interface SwitchProps {
}
export class Switch extends React.Component<SwitchProps, any> { }
export interface match<P> {
params: P;
export interface match<Params extends { [K in keyof Params]?: string } = {}> {
params: Params;
isExact: boolean;
path: string;
url: string;
@@ -116,7 +116,7 @@ export interface match<P> {
// Omit taken from https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export function matchPath<P>(pathname: string, props: RouteProps, parent?: match<P> | null): match<P> | null;
export function matchPath<Params extends { [K in keyof Params]?: string }>(pathname: string, props: RouteProps, parent?: match<Params> | null): match<Params> | null;
export function generatePath(pattern: string, params?: { [paramName: string]: string | number | boolean }): string;

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
interface TOwnProps extends RouteComponentProps<{}> {
interface TOwnProps extends RouteComponentProps {
username: string;
}

View File

@@ -67,9 +67,9 @@ const NavLink: React.SFC<LinkProps> = (props) => (
);
interface HSLParams {
h: number;
s: number;
l: number;
h: string;
s: string;
l: string;
}
const HSL: React.SFC<RouteComponentProps<HSLParams>> = ({ match: { params } }) => (

View File

@@ -65,10 +65,10 @@ const PrivateRoute: React.SFC<RouteProps> = ({ component, ...rest }) => (
)}/>
);
const Public: React.SFC<RouteComponentProps<{}>> = () => <h3>Public</h3>;
const Protected: React.SFC<RouteComponentProps<{}>> = () => <h3>Protected</h3>;
const Public: React.SFC<RouteComponentProps> = () => <h3>Public</h3>;
const Protected: React.SFC<RouteComponentProps> = () => <h3>Protected</h3>;
class Login extends React.Component<RouteComponentProps<{}>, {redirectToReferrer: boolean}> {
class Login extends React.Component<RouteComponentProps, {redirectToReferrer: boolean}> {
state = {
redirectToReferrer: false
};

View File

@@ -36,7 +36,7 @@ const About = () => (
</div>
);
const Topics: React.SFC<RouteComponentProps<{}>> = ({ match }) => (
const Topics: React.SFC<RouteComponentProps> = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>

View File

@@ -16,7 +16,7 @@ import {
// are the same as before but now we see them inside a modal
// on top of the old screen.
class ModalSwitch extends React.Component<RouteComponentProps<{}>> {
class ModalSwitch extends React.Component<RouteComponentProps> {
// We can pass a location to <Switch/> that will tell it to
// ignore the router's current location and use the location
// prop instead.
@@ -31,7 +31,7 @@ class ModalSwitch extends React.Component<RouteComponentProps<{}>> {
// is still `/` even though its `/images/2`.
previousLocation = this.props.location;
componentWillUpdate(nextProps: RouteComponentProps<{}>) {
componentWillUpdate(nextProps: RouteComponentProps) {
const { location } = this.props;
// set previousLocation if props.location is not modal
if (

View File

@@ -39,7 +39,7 @@ const Home = () => (
const WillMatch = () => <h3>Matched!</h3>;
const NoMatch: React.SFC<RouteComponentProps<{}>> = ({ location }) => (
const NoMatch: React.SFC<RouteComponentProps> = ({ location }) => (
<div>
<h3>No match for <code>{location.pathname}</code></h3>
</div>

View File

@@ -15,24 +15,28 @@ const PEEPS = [
{ id: 3, name: 'David', friends: [ 1, 2 ] }
];
const find = (id: number) => PEEPS.find(p => p.id === id);
const find = (id: string) => PEEPS.find(p => p.id === parseInt(id, 10));
// original code example relies upon == rather than === for comparison;
// allowing for string / number coercion in comparison; the change to use
// parseInt and === above makes this behaviour explicit
// const find = id => PEEPS.find(p => p.id == id);
const RecursiveExample = () => (
<Router>
<Person match={{ params: { id: 0 }, url: '' }}/>
<Person match={{ params: { id: '0' }, url: '' }}/>
</Router>
);
interface InitialPersonProps {
match: {
params: {
id: number;
id: string;
};
url: string;
};
}
type PersonProps = RouteComponentProps<{ id: number }>;
type PersonProps = RouteComponentProps<{ id: string }>;
const Person: React.SFC<InitialPersonProps | PersonProps> = ({ match }) => {
const person = find(match.params.id);
@@ -44,7 +48,7 @@ const Person: React.SFC<InitialPersonProps | PersonProps> = ({ match }) => {
{person!.friends.map(id => (
<li key={id}>
<Link to={`${match.url}/${id}`}>
{find(id)!.name}
{find(id.toString())!.name}
</Link>
</li>
))}

View File

@@ -17,7 +17,7 @@ const Main = () => <h2>Main</h2>;
const Sandwiches = () => <h2>Sandwiches</h2>;
interface PropsWithRoutes extends RouteComponentProps<{}> {
interface PropsWithRoutes extends RouteComponentProps {
routes: MyRouteProps[];
}

View File

@@ -13,7 +13,7 @@ import {
OnUpdateCall
} from "rrc";
class RouteOne extends React.Component<RouteComponentProps<{}>> {
class RouteOne extends React.Component<RouteComponentProps> {
render() {
return <div>
<ConfigSwitch location={this.props.location} routes={[
@@ -29,7 +29,7 @@ class RouteOne extends React.Component<RouteComponentProps<{}>> {
}
}
class RouteTwo extends React.Component<RouteComponentProps<{}>> {
class RouteTwo extends React.Component<RouteComponentProps> {
private readonly onUpdate: OnUpdateCall = (location) => { console.log("update"); };
render() {
@@ -64,7 +64,7 @@ interface Params {
page: number;
}
class RouteFour extends React.Component<RouteComponentProps<{}>> {
class RouteFour extends React.Component<RouteComponentProps> {
private readonly routes: RouteConfiguration[] = [
{ path: "/four/something/:page", component: RouteTwo }
];