Skip to content

Commit

Permalink
Expose router methods via context
Browse files Browse the repository at this point in the history
  • Loading branch information
taion committed Oct 31, 2016
1 parent 9b26597 commit bc0c9ee
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 24 deletions.
27 changes: 9 additions & 18 deletions src/BaseLink.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import elementType from 'react-prop-types/lib/elementType';

import { routerShape } from './PropTypes';

const propTypes = {
Component: elementType.isRequired,
to: React.PropTypes.oneOfType([
Expand All @@ -14,19 +16,10 @@ const propTypes = {
exact: React.PropTypes.bool.isRequired,
target: React.PropTypes.string,
onClick: React.PropTypes.func,
push: React.PropTypes.func.isRequired,
};

const contextTypes = {
store: React.PropTypes.shape({
farce: React.PropTypes.shape({
createHref: React.PropTypes.func.isRequired,
createLocation: React.PropTypes.func.isRequired,
}).isRequired,
found: React.PropTypes.shape({
isActive: React.PropTypes.func.isRequired,
}).isRequired,
}).isRequired,
router: routerShape.isRequired,
};

const defaultProps = {
Expand All @@ -36,7 +29,7 @@ const defaultProps = {

class BaseLink extends React.Component {
onClick = (event) => {
const { onClick, target, push, to } = this.props;
const { onClick, target, to } = this.props;

if (onClick) {
onClick(event);
Expand All @@ -62,7 +55,7 @@ class BaseLink extends React.Component {
// FIXME: When clicking on a link to the same location in the browser, the
// actual becomes a replace rather than a push. We may want the same
// handling – perhaps implemented in the Farce protocol.
push(to);
this.context.router.push(to);
};

render() {
Expand All @@ -77,13 +70,11 @@ class BaseLink extends React.Component {
...props
} = this.props;

const { farce, found } = this.context.store;

delete props.push; // Used in onClick.
const { router } = this.context;

if (activeClassName || activeStyle || activePropName) {
const toLocation = farce.createLocation(to);
const active = found.isActive(toLocation, match, { exact });
const toLocation = router.createLocation(to);
const active = router.isActive(toLocation, match, { exact });

if (active) {
if (activeClassName) {
Expand All @@ -104,7 +95,7 @@ class BaseLink extends React.Component {
return (
<Component
{...props}
href={farce.createHref(to)}
href={router.createHref(to)}
onClick={this.onClick}
/>
);
Expand Down
11 changes: 11 additions & 0 deletions src/PropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export const routerShape = React.PropTypes.shape({
push: React.PropTypes.func.isRequired,
replace: React.PropTypes.func.isRequired,
go: React.PropTypes.func.isRequired,

createHref: React.PropTypes.func.isRequired,
createLocation: React.PropTypes.func.isRequired,
isActive: React.PropTypes.func.isRequired,
});
35 changes: 34 additions & 1 deletion src/createBaseRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';

import getRoutes from './getRoutes';
import HttpError from './HttpError';
import { routerShape } from './PropTypes';
import RedirectException from './RedirectException';

export default function createBaseRouter({ routeConfig, matcher }) {
Expand All @@ -10,10 +11,19 @@ export default function createBaseRouter({ routeConfig, matcher }) {
matchContext: React.PropTypes.any, // eslint-disable-line react/no-unused-prop-types
resolveElements: React.PropTypes.func.isRequired, // eslint-disable-line react/no-unused-prop-types
render: React.PropTypes.func.isRequired,
replace: React.PropTypes.func.isRequired, // eslint-disable-line react/no-unused-prop-types
push: React.PropTypes.func.isRequired,
replace: React.PropTypes.func.isRequired,
go: React.PropTypes.func.isRequired,
createHref: React.PropTypes.func.isRequired,
createLocation: React.PropTypes.func.isRequired,
isActive: React.PropTypes.func.isRequired,
initialState: React.PropTypes.object,
};

const childContextTypes = {
router: routerShape.isRequired,
};

class BaseRouter extends React.Component {
constructor(props, context) {
super(props, context);
Expand All @@ -31,6 +41,28 @@ export default function createBaseRouter({ routeConfig, matcher }) {
}
}

getChildContext() {
const {
push,
replace,
go,
createHref,
createLocation,
isActive,
} = this.props;

return {
router: {
push,
replace,
go,
createHref,
createLocation,
isActive,
},
};
}

componentWillReceiveProps(nextProps) {
++this.matchIndex;
this.resolveMatch(nextProps);
Expand Down Expand Up @@ -94,6 +126,7 @@ export default function createBaseRouter({ routeConfig, matcher }) {
}

BaseRouter.propTypes = propTypes;
BaseRouter.childContextTypes = childContextTypes;

return BaseRouter;
}
8 changes: 6 additions & 2 deletions src/createConnectedLink.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import FarceActions from 'farce/lib/Actions';
import { connect } from 'react-redux';

import BaseLink from './BaseLink';
Expand All @@ -8,6 +7,11 @@ export default function createConnectedLink({
}) {
return connect(
state => ({ match: getMatch(state) }),
{ push: FarceActions.push },
null,
(stateProps, dispatchProps, ownProps) => ({
...ownProps,
...stateProps,
// We don't want dispatch here.
}),
)(BaseLink);
}
36 changes: 34 additions & 2 deletions src/createConnectedRouter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FarceActions from 'farce/lib/Actions';
import React from 'react';
import { connect } from 'react-redux';

import createBaseRouter from './createBaseRouter';
Expand All @@ -7,8 +8,39 @@ export default function createConnectedRouter({
getMatch = ({ match }) => match,
...options
}) {
return connect(
// createHref, createLocation, and isActive are taken directly from the store
// to avoid potential issues with middlewares in the chain messing with the
// return value from dispatch.
const propTypes = {
store: React.PropTypes.shape({
farce: React.PropTypes.shape({
createHref: React.PropTypes.func.isRequired,
createLocation: React.PropTypes.func.isRequired,
}).isRequired,
found: React.PropTypes.shape({
isActive: React.PropTypes.func.isRequired,
}).isRequired,
}).isRequired,
};

const ConnectedRouter = connect(
state => ({ match: getMatch(state) }),
{ replace: FarceActions.replace },
{
push: FarceActions.push,
replace: FarceActions.replace,
go: FarceActions.go,
},
(stateProps, dispatchProps, { store, ...ownProps }) => ({
...ownProps,
...stateProps,
...dispatchProps,
createHref: store.farce.createHref,
createLocation: store.farce.createLocation,
isActive: store.found.isActive,
}),
)(createBaseRouter(options));

ConnectedRouter.propTypes = propTypes;

return ConnectedRouter;
}
9 changes: 8 additions & 1 deletion src/createFarceRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,17 @@ export default function createFarceRouter({
this.store.dispatch(FarceActions.init());
}

componentWillUnmount() {
this.store.dispatch(FarceActions.dispose());
}

render() {
return (
<Provider store={this.store}>
<ConnectedRouter {...this.props} />
<ConnectedRouter
{...this.props}
store={this.store}
/>
</Provider>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export HttpError from './HttpError';
export Link from './Link';
export Matcher from './Matcher';
export matchReducer from './matchReducer';
export { routerShape } from './PropTypes';
export Redirect from './Redirect';
export RedirectException from './RedirectException';
export resolveElements from './resolveElements';
Expand Down

0 comments on commit bc0c9ee

Please sign in to comment.