AveroLLC / typesafe-react-router

Utility functions to help facilitate type-safe routing with react-router
Apache License 2.0
89 stars 14 forks source link

RouteParams & QueryParams #9

Open bbenezech opened 5 years ago

bbenezech commented 5 years ago

I love this lib, simple and focused.

I think you may want to spend another hour of work to:

User-land typesafe usage example of ReactRouter Route component:

import React from "react";
import { Route as RouteComponent, RouteProps, RouteComponentProps } from "react-router-dom";
import { RouteParams, Route } from "typesafe-react-router";

type QueryParams<T> = T extends Route<any, infer Y> ? Partial<Record<Y[number], string>> : never;

interface MatchProps<R extends Route<any, any>>
  extends Omit<RouteProps, "path" | "render" | "component" | "children"> {
  route: R;
  component?: React.ComponentType<RouteComponentProps<RouteParams<R>>>;
  render?: (
    params: RouteParams<R> & QueryParams<R>,
    allProps: RouteComponentProps<RouteParams<R>>
  ) => React.ReactNode;
  children?: (props: RouteComponentProps<RouteParams<R>>) => React.ReactNode;
}

// Simple typesafe ReactRoute `Route` component wrapper. Not nestable in ReactRouter `Switch` component.

// `render` prop.
// Renders only when matching.
// Similar to `ReactRouter Route#render`, but first argument is params/search_params hash:
//   Explicitely access match params/search_params hash.
//     <Match route={Routes.project.template()} render={({ projectId }) => <Project projectId={projectId} />} />
//   Project -since it is a function- gets the first render arg. (i.e. params/search_params) mixed in the props.
//   So the previous example can be written:
//     <Match route={Routes.project.template()} render={Project} />
//   Explicitely access ReactRouter props (second arg.).
//     <Match route={Routes.project.template()} render={(_, { history, location, match, staticContext }) => <Project projectId={match.params.projectId} /> } />

// `component` prop.
// Renders only when matching.
// Identical to `ReactRouter Route#component`:
// Project gets ReactRouter props mixed in his props. `projectId` is accessible with `props.match.params.projectId`.
//    <Match route={Routes.project.template()} component={Project} />

// `children` prop
// ALWAYS renders
// Identical to `ReactRouter Route#children`. Only the "function as a children" form is typesafe.
//   <Match route={Routes.project.template()}>
//     {({ history, location, match, staticContext }) =>
//       match ? <Project projectId={match.params.projectId} /> : null}
//   </Match>

const Match = <R extends Route<any, any>>({
  route,
  render,
  component,
  ...routeProps
}: MatchProps<R>) => (
  <RouteComponent
    {...routeProps}
    path={route.template()}
    render={props =>
      render
        ? render({ ...props.match.params, ...route.parse(props.location.search) }, props)
        : component
        ? React.createElement(component, props)
        : undefined
    }
  />
);

export default Match;
bbenezech commented 4 years ago

@jasonmendes I'm not a native speaker. you may want to translated literally in my langage is not pushy. It meant to sound like "if you have another spared hour to spend on this project, I think if I were you I would do this and that".