Skip to content

Commit 97e7f63

Browse files
committed
feat: improve Router
1 parent 7ed3af8 commit 97e7f63

File tree

6 files changed

+72
-40
lines changed

6 files changed

+72
-40
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const MyComponent = mock();
3232
- [Dummies](./docs/Dummies.md)
3333
- [`mock()`](./docs/mock.md) and [`loadable()`](./docs/loadable.md)
3434
- [`lazy()`](./docs/lazy.md), [`delayed()`](./docs/delayed.md), and [`viewport()`](./docs/viewport.md)
35-
- [Inversion](./docs/inversion.md)
35+
- [Inversion](./docs/Inversion.md)
3636
- [`invert()`](./docs/invert.md) and [`<Inverted>`](./docs/invert.md#inverted)
3737
- [`<State>`](./docs/State.md) and [`withState()`](./docs/State.md#withstate-hoc)
3838
- [`<Toggle>`](./docs/Toggle.md), [`withToggle()`](./docs/Toggle.md#withtoggle-hoc), and [`@withToggle`](./docs/Toggle.md#withtoggle-decorator)

docs/Introduction.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
`libreact` is a collection of most essential React utilities you will probably need in any project.
44

55
[Render prop](#render-props) and [FaCC](#facc) notation is usually used interchangeably as most `libreact`
6-
components support both interfaces.
6+
components support both interfaces. Also, most render prop components support
7+
[component prop](#component-prop) interface, with the following precedence:
8+
9+
1. FaCC
10+
2. Render prop
11+
3. Component prop
712

813

914
## Render props
@@ -55,11 +60,25 @@ h(MouseSensor, null, ({posX, posY}) => h('div')})
5560
```
5661

5762

63+
## Component prop
64+
65+
*Component prop* is when a component expects a `component` or `comp` prop that is
66+
itself a component.
67+
68+
```jsx
69+
<Route match='/home' comp={Home} />
70+
<Route match='/user' component={User} />
71+
```
72+
73+
`libreact` supports both ways of component prop (`comp` and `component`). Normally, when a component has a
74+
render prop interface it will also support the component prop interface.
75+
76+
5877
## HOC
5978

6079
HOC or *Higher Order Component* is a function that receives AND/OR returns React components.
6180

6281

6382
## Enhancer
6483

65-
*Enhancer* is a HOC that receives AND returns a React component.
84+
*Enhancer* is a HOC that receives AND returns a React components.

docs/inversion.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
>
55
> the action of inverting something or the state of being inverted.
66
7-
Inversion is a technique where one injects state and/or life-cycle methods into render-prop/FaCC.
7+
Inversion is a technique where one injects state and/or life-cycle methods into a render prop.
88

99
This effectively allows you to have
1010

@@ -16,13 +16,13 @@ This effectively allows you to have
1616
in JSX tree without having to write stateful React components.
1717

1818
- [`invert()`](./invert.md) and [`<Inverted>`](./invert.md#inverted) - inverts DOM element `ref`.
19-
- [`<State>`](./State.md) - inverts state and `.setState()` method.
19+
- [`<State>`](./State.md) - inverts `.state` and `.setState()` method.
2020
- [`<Toggle>`](./Toggle.md), [`<Flipflop>`](./Flipflop.md), [`<Value>`](./Value.md), [`<Couter>`](./Counter.md), [`<List>`](./List.md), and [`<Map>`](./Map.md)
2121

2222

2323
## Example
2424

25-
A basic example, `cnt` is incremented by 1 on each click:
25+
A basic example, `cnt` is incremented by 1 on each click
2626

2727
```jsx
2828
<State init={{cnt: 0}}>
@@ -33,3 +33,13 @@ A basic example, `cnt` is incremented by 1 on each click:
3333
}
3434
<State>
3535
```
36+
37+
or the same using `<Counter>` component
38+
39+
```jsx
40+
<Counter>{({value, inc}) =>
41+
<div onClick={inc}>
42+
{value}
43+
</div>
44+
}</Counter>
45+
```

src/route/__story__/StoryRouteExample.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {createElement as h} from 'react';
22
import {LocationSensor} from '../../LocationSensor';
3-
import {Router, Route} from '..';
3+
import {Router, Route, Route404} from '..';
44

55
const StoryRouteExample = () => (
66
<Router>
77
<div>
88
<ul>
9-
<li onClick={() => history.pushState(null, '', '/home.html')}>Home</li>
9+
<li onClick={() => history.pushState(null, '', '/home')}>Home</li>
1010
<li onClick={() => history.pushState(null, '', '/home/intro.html')}>Home / Intro</li>
1111
<li onClick={() => history.pushState(null, '', '/home/more.html')}>Home / More</li>
1212
<li onClick={() => history.pushState(null, '', '/page.html')}>Page</li>
@@ -16,12 +16,12 @@ const StoryRouteExample = () => (
1616
<Route match='/home'>{() =>
1717
<div>
1818
<div>HOME</div>
19-
<Route match='/intro' children={<div>INTRO</div>} />
20-
<Route children={<div>HOME/404</div>} />
19+
<Route match='/intro' children={<div>HOME/INTRO</div>} />
20+
<Route404 children={<div>HOME/404</div>} />
2121
</div>
2222
}</Route>
23-
<Route match={/^\/page\.html/} children={<div>PAGE</div>} />
24-
<Route children={<div>404</div>} />
23+
<Route match={/^\/page\.html/} render={<div>PAGE</div>} />
24+
<Route404 children={<div>404</div>} />
2525

2626
<br />
2727
<hr />
@@ -43,8 +43,8 @@ const StoryRouteExample = () => (
4343
<Route children={<div>HOME/404</div>} />
4444
</div>
4545
}</Route>
46-
<Route match={/^\/page\.html/} children={<div>PAGE</div>} />
47-
<Route children={<div>404</div>} />
46+
<Route match={/^\/page\.html/} render={<div>PAGE</div>} />
47+
<Route max={0} children={<div>404</div>} />
4848
`}</pre>
4949
</div>
5050
</Router>

src/route/index.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Component} from 'react';
22
import {LocationSensor} from '../LocationSensor';
33
import {Provider, Consumer} from '../context';
44
import {h, ns} from '../util';
5+
import renderProp from '../util/renderProp';
56

67
export interface IRouteProviderProps {
78
children?: any;
@@ -13,7 +14,7 @@ export interface IRouteProviderProps {
1314
export class Router extends Component<IRouteProviderProps, any> {
1415
matches: number = 0;
1516

16-
onMatch = () => {
17+
inc = () => {
1718
this.matches++;
1819
};
1920

@@ -25,8 +26,8 @@ export class Router extends Component<IRouteProviderProps, any> {
2526
name: ns(`route/${this.props.ns}`),
2627
value: {
2728
route,
28-
onMatch: this.onMatch,
29-
getMatches: () => this.matches,
29+
inc: this.inc,
30+
count: () => this.matches,
3031
parent: this.props.parent
3132
}
3233
}, Array.isArray(children) ? h('div', null, children) : children);
@@ -55,18 +56,21 @@ export type TRouteMatcher = (route: string) => TRouteMatchResult;
5556

5657
export interface IRouteMatch {
5758
children?: React.ReactElement<any> | ((params) => React.ReactElement<any>);
58-
cnt?: number;
59+
render?: React.ReactElement<any> | ((params) => React.ReactElement<any>);
5960
comp?: React.ComponentClass<any> | React.StatelessComponent<any>;
6061
exact?: boolean;
6162
match?: TRouteMatcher | RegExp | string;
63+
min?: number;
64+
max?: number;
6265
ns?: string;
6366
preserve?: boolean;
6467
}
6568

6669
export class Route extends Component<IRouteMatch, any> {
6770
static defaultProps = {
6871
match: /.+/,
69-
cnt: 0
72+
min: 0,
73+
max: Infinity
7074
};
7175

7276
matcher (): TRouteMatcher {
@@ -99,38 +103,30 @@ export class Route extends Component<IRouteMatch, any> {
99103
};
100104
}
101105

102-
renderChildren (props) {
103-
const {comp, children} = this.props;
104-
105-
return comp ?
106-
h(comp, props) :
107-
typeof children === 'function' ?
108-
children(props) :
109-
children;
110-
}
111-
112106
render () {
113-
return h(Consumer, {name: ns(`route/${this.props.ns}`)}, ({route, onMatch, getMatches, parent}) => {
114-
const {children, match, preserve} = this.props;
107+
return h(Consumer, {name: ns(`route/${this.props.ns}`)}, ({route, inc, count, parent}) => {
108+
const {children, match, preserve, min, max} = this.props;
109+
const matchCount = count();
110+
console.log('...');
111+
console.log('matchCount', matchCount);
115112

116-
if (getMatches() <= this.props.cnt) {
113+
if ((matchCount >= min) && (matchCount <= max)) {
117114
const matchResult = this.matcher()(route);
118115

119116
if (matchResult) {
117+
// Increment number of matched routes.
118+
inc();
119+
120120
(matchResult as any).parent = parent;
121121
const {matches, length} = matchResult;
122-
123-
// Notify <RouteProvider> that we matched.
124-
onMatch(this, matchResult);
125-
126122
let newRoute = route;
127123

128124
if (!preserve && length) {
129125
newRoute = newRoute.substr(length);
130126
}
131127

132128
return h(Router, {route: newRoute, parent: matchResult},
133-
this.renderChildren({
129+
renderProp(this.props, {
134130
match: route.substr(0, length),
135131
matches,
136132
route: newRoute,
@@ -145,4 +141,9 @@ export class Route extends Component<IRouteMatch, any> {
145141
}
146142
}
147143

144+
export const Route404 = (props) => h(Route, {
145+
max: 0,
146+
...props
147+
});
148+
148149
export * from './go';

src/util/renderProp.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {isFn} from '../util';
1+
import {h, isFn} from '../util';
22

33
const renderProp = (props, ...args) => {
44
if (process.env.NODE_ENV !== 'production') {
@@ -17,13 +17,15 @@ const renderProp = (props, ...args) => {
1717
}
1818
}
1919

20-
const {children, render} = props;
20+
const {children, comp, component, render} = props;
2121

2222
return isFn(children) ?
2323
children(...args) :
2424
isFn(render) ?
2525
render(...args) :
26-
(children || null);
26+
(comp || component) ?
27+
h(comp || component, args[0]) :
28+
(children || render || null);
2729
};
2830

2931
export default renderProp;

0 commit comments

Comments
 (0)