Skip to content

Commit 38a3d92

Browse files
committed
[state-router] Tolerate (but warn) on missing router context (#160)
1 parent 7931275 commit 38a3d92

File tree

14 files changed

+145
-80
lines changed

14 files changed

+145
-80
lines changed

packages/@sanity/state-router/demo-server/components/Main.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// @flow
2-
import type {ContextRouter} from '../../src/components/types'
2+
import type {Router} from '../../src/components/types'
33
import React from 'react'
44
import {StateLink, Link, RouteScope, withRouterHOC} from '../../src/components'
55
import Product from './Product'
66
import User from './User'
77

88
type Props = {
9-
router: ContextRouter
9+
router: Router
1010
}
1111

1212
export default withRouterHOC((props: Props) => {

packages/@sanity/state-router/demo-server/components/ProductCounter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// @flow
2-
import type {ContextRouter} from '../../src/components/types'
2+
import type {Router} from '../../src/components/types'
33
import React from 'react'
44
import withRouterHOC from '../../src/components/withRouterHOC'
55

66
type Props = {
7-
router: ContextRouter
7+
router: Router
88
}
99

1010
export default withRouterHOC((props: Props) => {

packages/@sanity/state-router/demo-server/components/SomeChild.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import PropTypes from 'prop-types'
21
import React from 'react'
32

43
export default class SomeChild extends React.Component {

packages/@sanity/state-router/demo-server/components/User.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
// @flow
12
import PropTypes from 'prop-types'
23
import React from 'react'
34
import StateLink from '../../src/components/StateLink'
45

5-
export default class User extends React.Component {
6+
export default class User extends React.Component<*> {
67
static propTypes = {
78
id: PropTypes.string
89
}

packages/@sanity/state-router/demo-server/entry.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ function handleNavigate(nextUrl, {replace} = {}) {
3333

3434
function render(state, pathname) {
3535
ReactDOM.render((
36-
<RouterProvider router={router} onNavigate={handleNavigate} state={state}>
37-
{router.isNotFound(pathname) ? <NotFound pathname={pathname} /> : <Main />}
38-
</RouterProvider>
36+
<div>
37+
<RouterProvider router={router} onNavigate={handleNavigate} state={state}>
38+
{router.isNotFound(pathname) ? <NotFound pathname={pathname} /> : <Main />}
39+
</RouterProvider>
40+
<div>
41+
<h2>Components outside provider context</h2>
42+
<Main />
43+
</div>
44+
</div>
3945
), document.getElementById('main'))
4046
}
4147

@@ -64,6 +70,7 @@ function checkPath() {
6470
handleNavigate(router.encode(handler.resolveRedirectState(state.intent, state.params)), {replace: true})
6571
return
6672
}
73+
// eslint-disable-next-line no-console
6774
console.log('No intent handler for intent "%s" with params %o', state.intent, state.params)
6875
}
6976
render(state || {}, pathname)
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,47 @@
1-
import PropTypes from 'prop-types'
21
// @flow
3-
import React, { Element } from 'react';
2+
import * as React from 'react'
43
import Link from './Link'
54
import type {RouterProviderContext} from './types'
5+
import internalRouterContextTypeCheck from './internalRouterContextTypeCheck'
66

7-
export default class IntentLink extends React.PureComponent {
7+
export default class IntentLink extends React.PureComponent<*, *> {
88
props: {
99
intent: string,
1010
params?: Object,
11-
children: Element<*>,
11+
children: React.Node,
1212
className: string
1313
};
1414

1515
context: RouterProviderContext
1616

1717
static contextTypes = {
18-
__internalRouter: PropTypes.object
18+
__internalRouter: internalRouterContextTypeCheck
1919
}
2020

21+
_element: Link
22+
2123
focus() {
2224
if (this._element) {
2325
this._element.focus()
2426
}
2527
}
2628

27-
setElement = element => {
28-
this._element = element
29+
setElement = (element: ?Link) => {
30+
if (element) {
31+
this._element = element
32+
}
33+
}
34+
35+
resolveIntentLink(intent: string, params?: Object) {
36+
if (!this.context.__internalRouter) {
37+
return `javascript://intent@${JSON.stringify({intent, params})}`
38+
}
39+
return this.context.__internalRouter.resolveIntentLink(intent, params)
2940
}
3041

3142
render() {
3243
const {intent, params, ...rest} = this.props
3344

34-
const url = this.context.__internalRouter.resolveIntentLink(intent, params)
35-
return <Link href={url} {...rest} ref={this.setElement} />
45+
return <Link href={this.resolveIntentLink(intent, params)} {...rest} ref={this.setElement} />
3646
}
3747
}

packages/@sanity/state-router/src/components/Link.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
1-
import PropTypes from 'prop-types'
21
// @flow
3-
import React from 'react'
2+
import * as React from 'react'
43
import {omit} from 'lodash'
54
import type {RouterProviderContext} from './types'
5+
import internalRouterContextTypeCheck from './internalRouterContextTypeCheck'
66

7-
function isLeftClickEvent(event : SyntheticMouseEvent) {
7+
function isLeftClickEvent(event : SyntheticMouseEvent<*>) {
88
return event.button === 0
99
}
1010

11-
function isModifiedEvent(event : SyntheticMouseEvent) {
11+
function isModifiedEvent(event : SyntheticMouseEvent<*>) {
1212
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
1313
}
1414

15-
export default class Link extends React.PureComponent {
15+
export default class Link extends React.PureComponent<*, *> {
1616
props: {
1717
replace?: boolean,
18-
onClick?: (event : SyntheticMouseEvent) => void,
18+
onClick?: (event : SyntheticMouseEvent<*>) => void,
1919
href: string,
2020
target?: string
2121
}
2222

2323
context: RouterProviderContext
24+
_element: HTMLAnchorElement
2425

2526
static defaultProps = {
2627
replace: false,
2728
}
2829

2930
static contextTypes = {
30-
__internalRouter: PropTypes.object
31+
__internalRouter: internalRouterContextTypeCheck
3132
}
3233

33-
handleClick = (event : SyntheticMouseEvent) : void => {
34+
handleClick = (event : SyntheticMouseEvent<*>) : void => {
35+
36+
if (!this.context.__internalRouter) {
37+
return
38+
}
3439

3540
if (event.isDefaultPrevented()) {
3641
return
@@ -62,8 +67,10 @@ export default class Link extends React.PureComponent {
6267
}
6368
}
6469

65-
setElement = element => {
66-
this._element = element
70+
setElement = (element: ?HTMLAnchorElement) => {
71+
if (element) {
72+
this._element = element
73+
}
6774
}
6875

6976
render() {

packages/@sanity/state-router/src/components/RouteScope.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import PropTypes from 'prop-types'
21
// @flow
3-
import React, { Element } from 'react';
2+
import PropTypes from 'prop-types'
3+
import * as React from 'react'
44
import isEmpty from '../utils/isEmpty'
55

66
import type {RouterProviderContext, NavigateOptions, InternalRouter} from './types'
77

8-
function addScope(routerState : Object, scope : string, scopedState : Object) {
8+
function addScope(routerState: Object, scope: string, scopedState: Object) {
99
return scopedState && {
1010
...routerState,
1111
[scope]: scopedState
@@ -14,18 +14,18 @@ function addScope(routerState : Object, scope : string, scopedState : Object) {
1414

1515
type Props = {
1616
scope: string,
17-
children: Element<*>
17+
children: React.Node
1818
}
1919

20-
export default class RouteScope extends React.Component {
20+
export default class RouteScope extends React.Component<*, *> {
2121
props: Props
2222
__internalRouter: InternalRouter
2323

2424
static childContextTypes = RouteScope.contextTypes = {
2525
__internalRouter: PropTypes.object
2626
}
2727

28-
constructor(props : Props, context : RouterProviderContext) {
28+
constructor(props: Props, context: RouterProviderContext) {
2929
super()
3030
const parentInternalRouter = context.__internalRouter
3131

@@ -37,7 +37,7 @@ export default class RouteScope extends React.Component {
3737
}
3838
}
3939

40-
getChildContext() : RouterProviderContext {
40+
getChildContext(): RouterProviderContext {
4141
return {
4242
__internalRouter: this.__internalRouter
4343
}
@@ -53,14 +53,14 @@ export default class RouteScope extends React.Component {
5353
const parentInternalRouter = this.context.__internalRouter
5454
const scope = this.props.scope
5555

56-
const nextStateScoped : Object = isEmpty(nextState)
56+
const nextStateScoped: Object = isEmpty(nextState)
5757
? {}
5858
: addScope(parentInternalRouter.getState(), scope, nextState)
5959

6060
return parentInternalRouter.resolvePathFromState(nextStateScoped)
6161
}
6262

63-
navigate = (nextState: Object, options?: NavigateOptions) : void => {
63+
navigate = (nextState: Object, options?: NavigateOptions): void => {
6464
const parentInternalRouter = this.context.__internalRouter
6565
const nextScopedState = addScope(parentInternalRouter.getState(), this.props.scope, nextState)
6666
parentInternalRouter.navigate(nextScopedState, options)

packages/@sanity/state-router/src/components/RouterProvider.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import PropTypes from 'prop-types'
21
// @flow
3-
import React, { Element } from 'react';
2+
import * as React from 'react'
3+
import PropTypes from 'prop-types'
44
import type {Router} from '../types'
55
import type {RouterProviderContext, NavigateOptions, InternalRouter, RouterState} from './types'
66
import pubsub from 'nano-pubsub'
77

88
type Props = {
9-
onNavigate: (nextPath: string) => void,
9+
onNavigate: (nextPath: string, options?: NavigateOptions) => void,
1010
router: Router,
1111
state: RouterState,
12-
children?: Element<*>
12+
children: React.Node
1313
}
1414

15-
export default class RouterProvider extends React.Component {
15+
export default class RouterProvider extends React.Component<*, *> {
1616
props: Props
1717

1818
static childContextTypes = {
@@ -45,7 +45,7 @@ export default class RouterProvider extends React.Component {
4545
this.navigateUrl(this.resolvePathFromState(nextState), options)
4646
}
4747

48-
navigateIntent = (intentName : string, params : Object, options : NavigateOptions = {}) : void => {
48+
navigateIntent = (intentName : string, params? : Object, options? : NavigateOptions = {}) : void => {
4949
this.navigateUrl(this.resolveIntentLink(intentName, params), options)
5050
}
5151

@@ -55,8 +55,8 @@ export default class RouterProvider extends React.Component {
5555
return this.props.router.encode(state)
5656
}
5757

58-
resolveIntentLink = (intent : string, params? : Object) : string => {
59-
return this.props.router.encode({intent, params})
58+
resolveIntentLink = (intentName : string, params?: Object) : string => {
59+
return this.props.router.encode({intent: intentName, params})
6060
}
6161

6262
getChildContext() : RouterProviderContext {

packages/@sanity/state-router/src/components/StateLink.js

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
import PropTypes from 'prop-types'
21
// @flow
32
import React from 'react'
4-
import {omit} from 'lodash'
53
import Link from './Link'
64
import type {RouterProviderContext} from './types'
5+
import internalRouterContextTypeCheck from './internalRouterContextTypeCheck'
76

87
const EMPTY_STATE = {}
98

10-
export default class StateLink extends React.PureComponent {
9+
export default class StateLink extends React.PureComponent<*, *> {
1110
props: {
1211
state?: Object,
1312
toIndex?: boolean
1413
}
1514
context: RouterProviderContext
15+
_element: Link
1616

1717
static defaultProps = {
1818
replace: false,
1919
toIndex: false,
2020
}
2121

2222
static contextTypes = {
23-
__internalRouter: PropTypes.object
23+
__internalRouter: internalRouterContextTypeCheck
2424
}
2525

2626
resolveUrl() : string {
@@ -37,7 +37,14 @@ export default class StateLink extends React.PureComponent {
3737

3838
const nextState = toIndex ? EMPTY_STATE : (state || EMPTY_STATE)
3939

40-
return this.context.__internalRouter.resolvePathFromState(nextState)
40+
return this.resolvePathFromState(nextState)
41+
}
42+
43+
resolvePathFromState(state: Object) {
44+
if (!this.context.__internalRouter) {
45+
return `javascript://state@${JSON.stringify(state)}`
46+
}
47+
return this.context.__internalRouter.resolvePathFromState(state)
4148
}
4249

4350

@@ -47,8 +54,10 @@ export default class StateLink extends React.PureComponent {
4754
}
4855
}
4956

50-
setElement = element => {
51-
this._element = element
57+
setElement = (element: ?Link) => {
58+
if (element) {
59+
this._element = element
60+
}
5261
}
5362

5463
render() {
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
// @flow
2-
import {Element} from 'react'
3-
import type {ContextRouter} from './types'
2+
import * as React from 'react'
3+
import type {Router} from './types'
44
import withRouterHOC from './withRouterHOC'
55

66
type Props = {
7-
router: ContextRouter,
8-
children: (ContextRouter) => Element<*>
7+
router: Router,
8+
children: (Router) => React.Node
99
}
1010

11-
const WithRouter = withRouterHOC((props : Props) => props.children(props.router))
12-
WithRouter.displayName = 'WithRouter'
11+
const WithRouter = withRouterHOC((props: Props) => props.children(props.router))
1312

1413
export default WithRouter
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default function internalRouterContextTypeCheck(context, propName, componentName) {
2+
if (!context.__internalRouter) {
3+
throw new Error(
4+
'The router is accessed outside the context of a <RouterProvider>.'
5+
+ ' No router state will be accessible and links will not go anywhere. To fix this,'
6+
+ ` make sure ${componentName} is rendered in the context of a <RouterProvider /> element`
7+
)
8+
}
9+
}

0 commit comments

Comments
 (0)