Skip to content

Commit

Permalink
feat: 🎸 Make new version of Route work
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 9, 2018
1 parent d32fde9 commit 77ebb0e
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 114 deletions.
71 changes: 71 additions & 0 deletions src/route/Route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import render from '../util/renderProp';
import Router from './Router';
import {Consumer} from '../context';
import {h, ns} from '../util';
import createMatcher, {TRouteMatcher, TRouteMatchResult} from './createMatcher';
import {IUniversalInterfaceProps} from '../typing';

export interface IRouteData {
fullRoute: string;
route: string;
parent: IRouteData;
match: TRouteMatchResult;
}

export interface IRouteProps extends IUniversalInterfaceProps<IRouteData> {
ns?: string;
exact?: boolean;
match?: TRouteMatcher | RegExp | string;
truncate?: boolean;
}

const Route: React.SFC<IRouteProps> = (props) => {
if (process.env.NODE_ENV !== 'production') {
if (typeof props.match !== 'string') {
if (props.exact) {
console.warn(
'You are using <Route exact /> with non-string match prop, ' +
'exact prop works only with string match prop.'
);
}
}
}

return h(Consumer, {name: ns(`route/${props.ns}`)}, (context) => {
const {exact, match, truncate} = props;
const {fullRoute, route, parent} = context;
const matches = createMatcher(match, exact)(route);

if (!matches) {
return null;
}

const data = {
fullRoute,
route,
parent,
matches,
};

let element = render(props, data);

if (truncate) {
const routerProps = {
fullRoute: route,
route: route.substr(matches[0].length),
parent: data,
} as any;

element = h(Router, routerProps, element);
}

return element;
});
};

Route.defaultProps = {
match: '',
exact: false
};

export default Route;
39 changes: 23 additions & 16 deletions src/route/Switch.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import {Component, Children} from 'react';
import {Children} from 'react';
import {Consumer} from '../context';
import {h, ns} from '../util';
import Route from './Route';
import createMatcher from './createMatcher';

const Switch = ({children}) => {
const routes = Children.toArray(children);
export interface ISwitchProps {
ns?: string;
}

let route = null;
const Switch: React.SFC<ISwitchProps> = (props) => {
return h(Consumer, {name: ns(`route/${props.ns}`)}, (route) => {
const routes = Children.toArray(props.children);

for (let i = 0; i < routes.length; i++) {
if (process.env.NODE_ENV !== 'production') {
// TODO: check that each element is a Route.
}

const {match, exact} = (routes[i] as any).props;
const matchResult = createMatcher(match, exact)(route);
for (let i = 0; i < routes.length; i++) {
if (process.env.NODE_ENV !== 'production') {
if ((typeof routes[i] !== 'object') || ((routes[i] as any).type !== Route)) {
throw new TypeError('All <Switch> children must be <Route> elements.');
}
}

if (matchResult) {
route = routes[i];
const {match, exact} = (routes[i] as any).props;
const matchResult = createMatcher(match, exact)(route);

break;
if (matchResult) {
return routes[i];
}
}
}

return route;
return null;
});
};

export default Switch;
20 changes: 19 additions & 1 deletion src/route/__story__/story.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {createElement as h} from 'react';
import {storiesOf} from '@storybook/react';
import ShowDocs from '../../../.storybook/ShowDocs'
import {Router} from '..';
import {Router, Route} from '..';
import StoryRouteExample from './StoryRouteExample';

storiesOf('Context/route', module)
Expand All @@ -13,4 +13,22 @@ storiesOf('Context/route', module)
<div>children</div>
</Router>
)
.add('Matches a route', () =>
<Router route='/test'>
<Route match='/test'>
<div>Children</div>
</Route>
<Route match='/test'>{() =>
<div>Facc</div>
}</Route>
<Route match='/test' render={() => <div>Render prop</div>} />
<Route match='/test' comp={() => <div>Component prop 1</div>} />
<Route match='/test' component={() => <div>Compnent prop 2</div>} />


<Route match='/no-match'>
<div>THIS SHOUDL NOT MATCH</div>
</Route>
</Router>
)
.add('Example 1', () => <StoryRouteExample />)
28 changes: 5 additions & 23 deletions src/route/createMatcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
export interface TRouteMatchResult {
length: number; // Length how many characters to truncate from route.
matches?: RegExpMatchArray; // RegExp matches, if any.
}
export type TRouteMatchResult = RegExpMatchArray | string[];

export type TRouteMatcher = (route: string) => TRouteMatchResult;

Expand All @@ -10,24 +7,9 @@ export default function createMatcher (match: string | RegExp | TRouteMatcher, e
return match;
}

let regex: RegExp;

if (typeof match === 'string') {
regex = new RegExp(`^(${match}${exact ? '$' : ''})`);
} else {
regex = match;
}

return (route: string) => {
const matches = route.match(regex);

if (!matches) {
return null;
}
let regex = typeof match === 'string'
? new RegExp(`^(${match}${exact ? '$' : ''})`)
: match;

return {
length: (matches && matches[1]) ? matches[1].length : 0,
matches
};
};
return (route: string) => route.match(regex);
}
77 changes: 4 additions & 73 deletions src/route/index.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,12 @@
import {Component} from 'react';
import {render} from 'react-universal-interface';
import Router from './Router';
import {Consumer} from '../context';
import {h, ns} from '../util';
import Route from './Route';
import Switch from './Switch';
import {go, Go} from './go';
import createMatcher, {TRouteMatcher} from './createMatcher';

export {
Router,
Route,
Switch,
go,
Go,
};

export interface IRouteMatch {
children?: any;
render?: React.ReactElement<any> | ((params) => React.ReactElement<any>);
comp?: React.ComponentClass<any> | React.StatelessComponent<any>;
component?: React.ComponentClass<any> | React.StatelessComponent<any>;
exact?: boolean;
match?: TRouteMatcher | RegExp | string;
min?: number;
max?: number;
ns?: string;
preserve?: boolean;
}

export class Route extends Component<IRouteMatch, any> {
static defaultProps = {
match: /.+/,
min: 0,
max: Infinity
};

render () {
return h(Consumer, {name: ns(`route/${this.props.ns}`)}, (context) => {
const {fullRoute, route, go, inc, count, parent} = context;
const {exact, match, preserve, min, max} = this.props;
const matchCount = count();

if ((matchCount >= min) && (matchCount <= max)) {
const matchResult = createMatcher(match, exact)(route);

if (matchResult) {
// Increment number of matched routes.
inc();

(matchResult as any).parent = parent;
const {matches, length} = matchResult;
let newRoute = route;

if (!preserve && length) {
newRoute = newRoute.substr(length);
}

return h(Router, {
fullRoute: route,
route: newRoute,
parent: matchResult
} as any,
render(this.props, {
go,
match: route.substr(0, length),
matches,
route: newRoute,
fullRoute,
parent
})
);
}
}

return null;
});
}
}

export const Route404 = (props) => h(Route, {
max: 0,
...props
});
2 changes: 1 addition & 1 deletion src/typing.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type TComponent<TProps> = React.Component<TProps, any> | React.SFC<TProps>;

export interface IUniversalInterfaceProps<TData> {
children?: ((data: TData) => React.ReactElement<any>) | React.ReactElement<TData & any>;
children?: ((data: TData) => React.ReactElement<any>) | React.ReactElement<TData & any> | any;
render?: (data: TData) => React.ReactElement<any>;
comp?: React.ComponentClass<TData> | React.SFC<TData>;
component?: React.ComponentClass<TData> | React.SFC<TData>;
Expand Down

0 comments on commit 77ebb0e

Please sign in to comment.