Skip to content

Instantly share code, notes, and snippets.

@gaelollivier
Forked from steida/Root.jsx
Last active October 6, 2016 09:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gaelollivier/050e5cce31f9fabb1030f4ba47663db5 to your computer and use it in GitHub Desktop.
Save gaelollivier/050e5cce31f9fabb1030f4ba47663db5 to your computer and use it in GitHub Desktop.
React Router v4 ControlledRouter
// @flow
import React, { Component } from 'react'
import BrowserHistory from 'react-history/BrowserHistory'
import { Push } from 'react-history'
import { StaticRouter } from 'react-router'
/**
* ControlledRouter, greatly inspired by https://gist.github.com/donnanicolas/3d76397a92551f449637590bf0413133
* Usage:
* <ControlledRouter location={location} setLocation={setLocation}>
* <App />
* </ControlledRouter>
*/
type PropTypes = {
location: Object,
setLocation: Function,
children?: React.Element<*>,
}
class ControlledRouter extends Component {
props: PropTypes
prevPathname: string
constructor(props: PropTypes) {
super(props)
this.prevPathname = ''
}
render() {
const { location, setLocation, children } = this.props
return (
<BrowserHistory
key={ location.pathname }
>
{({ history, action, location: historyLocation }) => {
const historyPathname = historyLocation.pathname
const controlledPathname = location.pathname
const pathChanged = historyPathname !== controlledPathname
const shouldUpdateState = pathChanged && historyPathname !== this.prevPathname
const shouldUpdateHistory = pathChanged && !shouldUpdateState
// Keep track of previous pathname
this.prevPathname = historyLocation.pathname
if (shouldUpdateState) {
setTimeout(() => {
setLocation(historyLocation)
}, 0)
}
return (
<StaticRouter
action={action}
location={historyLocation}
onPush={history.push}
onReplace={history.replace}
blockTransitions={history.block}
>
{ shouldUpdateHistory ? <Push path={location.pathname} /> : children }
</StaticRouter>
);
}}
</BrowserHistory>
)
}
}
ControlledRouter.displayName = 'ControlledRouter'
export default ControlledRouter
@jbraithwaite
Copy link

jbraithwaite commented Oct 6, 2016

This is so close but cause issues. Specifically the setTimeout with setLocation inside. I get why you did it: React complains loudly when you update state during a render but this method has insidious effects. For example, a child component will sometimes unmount and then render…

Quick fix here: https://gist.github.com/jbraithwaite/4c4ecfbc8a09d63e83d86c6810e0ec77

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment