Skip to content
This repository was archived by the owner on Oct 26, 2018. It is now read-only.

5.0 (React Router 4.0 integration) - #460

Closed
timdorr wants to merge 10 commits into
reactjs:masterfrom
timdorr:5.0.x
Closed

timdorr wants to merge 10 commits into
reactjs:masterfrom
timdorr:5.0.x

Conversation

@timdorr

@timdorr timdorr commented Sep 24, 2016

Copy link
Copy Markdown
Contributor

Best viewed here: https://github.com/timdorr/react-router-redux/tree/5.0.x

This revamps the library for React Router 4.0 integration.

This is one potential option for how we might integrate things. If not, we can definitely use this scaffolding as a base for something else.

Currently, this does one-way synchronization of the location from a <*History> (from react-history) to a Redux store. I could have done a more generic <ControlledRouter> that exposes an onChange prop, but this library has a specific purpose and I wanted to avoid excess boilerplate. The only complication is needing to add in an additional reducer. Not really a way to avoid that, but everyone should be used to that from the previous versions.

Next up is being able to dispatch history actions into the store to issue navigation events.

Anyways, this is just a beginning and it's in a very mercurial state, so suggestions are definitely welcome.

@timdorr timdorr changed the title 5.0 5.0 (React Router 4.0 integration) Sep 24, 2016
Comment thread modules/ConnectedRouter.js Outdated
store.dispatch({
type: LOCATION_CHANGE,
payload: { action, location }
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this cause infinite re-renders? It's firing even if the location is the same and that will cause another render.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely. I haven't given it a full rundown of tests. I'm thinking of potentially implementing an interim component that can use cWRP to do the location change.

Comment thread modules/ConnectedRouter.js Outdated
const { history:History, basename, keyLength, ...props } = this.props

return (
<History location={location} basename={basename} keyLength={keyLength}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

location is not defined

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!

Comment thread modules/ConnectedRouter.js Outdated
{({ history, action, location }) => {
const store = this.context.store || this.props.store

store.dispatch({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes a warning to be logged in the console

Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will likely move this to an interim component and use cWRP, which should address the issue.

@kyeotic kyeotic Sep 27, 2016 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use some cleanup, but I tested it and it works without producing any warnings

class ConnectedRouter extends Component {
  static propTypes = {
    store: PropTypes.object,
    history: PropTypes.func,
    basename: PropTypes.string,
    keyLength: PropTypes.number
  }

  static contextTypes = {
    store: PropTypes.object
  }

  static defaultProps = {
    history: BrowserHistory
  }

  render () {
    const { history: History, basename, keyLength, ...props } = this.props
    const store = this.context.store || this.props.store
    return (
      <History location={props.location} basename={basename} keyLength={keyLength}>
        {({history, action, location}) => (
          <BaseRouter {...{history, action, location, basename, ...props, store}} />) }
      </History>
    )
  }
}

class BaseRouter extends Component {
  componentDidMount () {
    this.updateLocation(this.props.location, this.props.action)
  }

  componentWillReceiveProps (nextProps) {
    this.updateLocation(nextProps.location, nextProps.action)
  }

  updateLocation = (location, action) => {
    this.props.store.dispatch({
      type: LOCATION_CHANGE,
      payload: { location, action }
    })
  }

  render () {
    const { history, action, location, basename } = this.props
    return (
      <StaticRouter
        action={action}
        location={location}
        basename={basename}
        onPush={history.push}
        onReplace={history.replace}
        blockTransitions={history.block}
        {...this.props}
      />)
  }
}


return (
<History location={props.location} basename={basename} keyLength={keyLength}>
<History basename={basename} keyLength={keyLength}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you intend to remove location?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's not used on *History in react-history.

Comment thread modules/ConnectedRouter.js Outdated
store: PropTypes.object,
history: PropTypes.object,
action: PropTypes.string,
location: PropTypes.string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

location is an object

@kyeotic

kyeotic commented Sep 27, 2016

Copy link
Copy Markdown

Looks, good. I'm running this without any issues.

@kevinold

kevinold commented Oct 3, 2016

Copy link
Copy Markdown

FYI, Ryan posted his ControlledRouter here: remix-run/react-router#3905 (comment). Should pieces of his implementation be rolled into here?

@kevinold

kevinold commented Oct 5, 2016

Copy link
Copy Markdown

@tyrsius How did you install this version into your project? I have installed this branch via npm git, as well as locally with npm link and I continue to see Module not found: Error: Cannot resolve module 'react-router-redux' when requiring the module into index.js.

Anything you could share would be much appreciated!

@kyeotic

kyeotic commented Oct 5, 2016 •

Copy link
Copy Markdown

@kevinold I didn't. Without the redux actions I had no way to update the history object. I copied the ControlledRouter and reducer from the repo tim posted at the top, and made some changes to capture the history object. It's alpha software, you're going to have to tweak it a bit

@timdorr

timdorr commented Oct 5, 2016

Copy link
Copy Markdown
Contributor Author

If you check it out as a git submodule, you can just link to the file on the filesystem directly for now. You should only have to do that in one or two places. So something like import { ConnectedRouter } from '../lib/react-router-redux/src'

@kevinold

kevinold commented Oct 5, 2016

Copy link
Copy Markdown

Thanks @tyrsius and @timdorr. I did see that there wasn't anything to run "build-lib" so I'm thinking I can add a preinstall to do that so that I can install it as a module. Testing that now and will send a PR if that works (and is wanted at this stage).

@kevinold

kevinold commented Oct 6, 2016

Copy link
Copy Markdown

@tyrsius Any chance you could share the changes you made to capture the history object?

I've restored to your approach in copying the ControlledRouter and reducer into my project.

I am seeing the LOCATION_CHANGE dispatched, but the history object is not updated. Any help is appreciated!

@kyeotic

kyeotic commented Oct 6, 2016 •

Copy link
Copy Markdown

Its _extremely_ hacky, but it was easy because I was already capturing and exposing the history object as a singleton in a local module, so instead of just exporting the object constructed from createBrowserHistory, I did this. It meant not having to change any of my existing actions that were importing history and calling push or replace on it

(reminder, this is a hacky stopgap until the actions are part of this module)

-the history module

import { createHistory } from 'history'

let _history = createHistory()

export default {
  bind: (history) => {
    _history = history
  },
  push: (...args) => _history.push(...args),
  listen: (...args) => _history.listen(...args),
  replace: (...args) => _history.replace(...args)
}

my ConnectedRouter

import React, { Component, PropTypes } from 'react'
import BrowserHistory from 'react-history/BrowserHistory'
import StaticRouter from 'react-router/StaticRouter'

import { LOCATION_CHANGE } from './reducer'

import history from 'PATH_TO_YOUR_MODULE/history.js'

class DispatchingRouter extends Component {
  static propTypes = {
    store: PropTypes.object,
    history: PropTypes.oneOfType([ PropTypes.object, PropTypes.string ]),
    action: PropTypes.string,
    location: PropTypes.object,
    basename: PropTypes.string
  }

  constructor (props) {
    super(props)
    history.bind(props.history)
    this.handleLocationChange(this.props)
  }

  componentWillReceiveProps (nextProps) {
    this.handleLocationChange(nextProps)
  }

  handleLocationChange = ({ store, action, location }) => {
    store.dispatch({
      type: LOCATION_CHANGE,
      payload: { action, location }
    })
  }

  render () {
    const { history, action, location, basename, ...props } = this.props

    return (
      <StaticRouter
        action={action}
        location={location}
        basename={basename}
        onPush={history.push}
        onReplace={history.replace}
        blockTransitions={history.block}
        {...props}
      />)
  }
}

const fixHistory = (history) => {
  history.location.search = history.location.search || ''
  history.location.hash = history.location.hash || ''
  return history
}

class ConnectedRouter extends Component {
  static propTypes = {
    store: PropTypes.object,
    history: PropTypes.func,
    basename: PropTypes.string,
    keyLength: PropTypes.number
  }

  static contextTypes = {
    store: PropTypes.object
  }

  static defaultProps = {
    history: BrowserHistory
  }

  render () {
    const { history: History, basename, keyLength, ...props } = this.props

    return (
      <History basename={basename} keyLength={keyLength}>
        {({ history, action, location }) => {
          return (
            <DispatchingRouter
              store={this.context.store || this.props.store}
              history={fixHistory(history)}
              action={action}
              location={location}
              basename={basename}
              {...props}
            />)
        }}
      </History>
    )
  }
}

export default ConnectedRouter

@kevinold

kevinold commented Oct 6, 2016

Copy link
Copy Markdown

@tyrsius Thanks. I have your code working, but I'm still not seeing the change when calling history.pushState() manually, since I cannot get the history object to come down to my Wrapper component.

Here's my code in index.js that calls ConnectedRouter:

ReactDOM.render(
      <Provider store={store}>
        <ConnectedRouter>
          <Match pattern="/" component={Wrapper} />
        </ConnectedRouter>
      </Provider>,
    document.getElementById('wrapper')
  );

I do not receive onPush, onReplace, etc. that are defined on StaticRouter or history that is defined on DispatchingRouter.

screen shot 2016-10-06 at 3 39 37 pm

screen shot 2016-10-06 at 3 39 49 pm

My question is, if I am suppose to call onPush() or history.push() directly, how am I to get it passed down to to my Matched components?

@kyeotic

kyeotic commented Oct 6, 2016 •

Copy link
Copy Markdown

@kevinold ... I told you it was hacky. This is what I am doing, it works. I don't recommend it.

Root.js

import Router from 'devportal/root/routes.js'
import App from 'devportal/root/App.js'

export default class Root extends Component {
  render () {
    return (
      <Provider store={this.props.store}>
        <Router component={App} />
      </Provider>
    )
  }
}

Routes.js

const Routes = ({component}) => {
  var Component = component
  return (
    <ConnectedRouter>
      <Match pattern='/' render={(props) => (
        <Component {...props}>
          <Match pattern='/' exactly component={SearchPage} />
          //  A bunch more matches
        </Component>
      )} />
    </ConnectedRouter>
  )
}
export default Routes

App.js

@connect(null, { /* actions */ })
export default class App extends Component {
  render () {
    return (
       <div className='app-container'>
          <NavBar />
          <div className='app-body'>
            {this.props.children}
          </div>
          <Footer />
        </div>
    )
  }
}

With all of this, calling history.push() from the history module I posted earlier should update the URL and rerender the <Match>es

@kevinold

kevinold commented Oct 6, 2016

Copy link
Copy Markdown

Thanks @tyrsius, I think I see what you're doing here. I'll keep hacking around with it. I appreciate your help!

@kevinold

kevinold commented Oct 6, 2016

Copy link
Copy Markdown

Unfortunately, I am not able to get onPush, onReplace, etc. as props to my Wrapper component :(

@gmaclennan

Copy link
Copy Markdown

I experimented with an alternative approach to this: https://github.com/gmaclennan/react-router-sync

It's pretty simple, and I may well be missing some of the complexity of react-router that I do not understand. See: https://github.com/gmaclennan/react-router-sync/blob/master/index.js

It's just a regular React component that keeps a global state in sync by either calling onChange or by rendering a <Redirect>.

I'd be interested to hear why this might not be a good approach. Not published on npm or anything, it's just proof of concept, but you can clone the repo and run the manual test and the redux example.

@jeffbski

Copy link
Copy Markdown

This branch seems to be working well for me too.

@supasate

supasate commented Dec 12, 2016 •

Copy link
Copy Markdown

Based on several ideas here, I've made ConnectedRouter PoC (https://github.com/supasate/connected-react-router) which supports dispatching history actions (e.g. push) and also works with react-hot-loader v3. (I also made an npm package to be easy to install and test.)

It works quite well in my use-case but, as a PoC, I may overlook several cases and need to check more.

However, the time travel feature is still broken (but redux devtools works if we don't rewind).

@pete-otaqui

pete-otaqui commented Dec 15, 2016 •

Copy link
Copy Markdown

I believe this is broken by react-histpry's latest (0.17.0) version:

ReactTraining/react-history@258dec2#diff-4ee6183afe47173284dec5a54c9bdc90L57

it no longer passes different props to the children function

Specifically, this function-as-children callback:

{({ history, action, location }) => (

Will no longer receive an object containing keys for history, action and location ... it will only receive a single "history"

@odigity

odigity commented Dec 16, 2016

Copy link
Copy Markdown

Just wanted to chime in with my support for this effort. I just started using React a month ago, and chose to learn and use React Router v4 since it was already out, rather than investing in a version who's EOL is visible. Now I'm studying Redux in preparation for adopting that as well, and it'd be nice to use the react-router-redux bindings, but I won't be able to until Router v4 is supported.

@csillag

csillag commented Dec 20, 2016

Copy link
Copy Markdown

@timdorr, do you think that there is real hope that you will be able to finish this during the holiday season? (Not demanding anything, just asking :) )

@csillag

csillag commented Dec 20, 2016

Copy link
Copy Markdown

@lourd, @supasate, @gmaclennan, do you think it makes sense to use your versions in a real (commercial) project? Are you doing the same? How long do you plan on maintaining them? Thanks.

@supasate

supasate commented Dec 20, 2016 •

Copy link
Copy Markdown

@csillag I'm using and maintaining connected-react-router for our new customer project. The project is still in development phase, so, it's not on production yet but it has been working very well so far since I launched it.

I plan to maintain it for a long run. I'm also happy to give it to reactjs/react-router-redux if @timdorr and the maintaining team think this PoC works for the whole community.

@jirikolarik

Copy link
Copy Markdown

@supasate works like a charm! Many thanks

@csillag

csillag commented Dec 20, 2016

Copy link
Copy Markdown

@supasate: Great to hear that! So that project is definitely one of my top candidates now.

On the other hand, earlier @lourd wrote:

I've been using the approach I mentioned at my job in our upcoming project for a couple weeks and haven't had issues so far. I would recommend it over the react-router-addons-controlled library. Source code here for reference.

@lourd, is that still the case?

@gmaclennan

gmaclennan commented Dec 20, 2016 via email

Copy link
Copy Markdown

@lourd

lourd commented Dec 20, 2016

Copy link
Copy Markdown

Hey @csillag. Yes, that's still the case. The project has been in QA for a couple weeks and went into production this week. We haven't had to change any of the react-router-redux example code that I posted earlier, it has been reliable and bug free. I can confidently recommend it.

The main interface you'll interact with is the action creators, and those methods are not likely to change at all regardless of what version of React Router or RRR you're using since they correspond with the the methods that history has. Your 'business logic' won't need to change in response to what integration configuration the library goes with.

The configuration code between history, the store, and the router may change slightly from what my code does now depending on what @timdorr thinks, but it will not be more than 5-10 lines of code.

@csillag

csillag commented Dec 20, 2016 via email

Copy link
Copy Markdown

@lourd

lourd commented Dec 20, 2016

Copy link
Copy Markdown

No, it's not. My project has the code in a local folder right now. I didn't want to make a separate package as to kind of encourage me to clean it up and make an official PR here.

@timdorr

timdorr commented Dec 21, 2016

Copy link
Copy Markdown
Contributor Author

I wouldn't work on any redux integration yet. The 4.0 API is still in flux and both Michael and Ryan have different potential implementations at the moment, neither of which match this branch. I will have to rewrite this once the API is settled on.

@lourd

lourd commented Dec 21, 2016

Copy link
Copy Markdown

Uhh... are you referring to the v4-alt branch on React Router? It looks like a distinctly different direction than the current v4 alpha. The change to a react-history dependency, the routeWillChange lifecycle method, no more Miss component, the imperative usage of the history instance from context in Link...

@mjackson, what's the motivation behind the alternative implementation?

@supasate

supasate commented Dec 23, 2016 •

Copy link
Copy Markdown

I just released connected-react-router v2.0.0-alpha.1.

Currently, it already supports Redux DevTools Time Travel (thanks to @dictions). It contains breaking change from v1.0.0-alpha.x but it also helps set up easier. So, for anyone who is using it, please see the migration guide here.

[Update] v2.0.0-alpha.2 is released. Now the browser URL is synced in Time Travel mode.

@csillag

csillag commented Jan 6, 2017

Copy link
Copy Markdown

Good news: I don't know what the reason is, but the v4-alt branch of the react-router is gone!
Hopefully, that means that the v4 API has been agreed upon.

@lourd

lourd commented Jan 6, 2017

Copy link
Copy Markdown

Haha, not quite. It looks like most of the work from v4-alt is in the v4 branch now. API changes ahead. Main one looks like Match -> Route, pattern -> path.

@csillag

csillag commented Jan 6, 2017

Copy link
Copy Markdown

Ooh, so in the end, the v4-alt branch "won"?

@lourd

lourd commented Jan 6, 2017

Copy link
Copy Markdown

Yeah, looks like. react-history is now a dependency, and Router accesses history through context. 😕 Can't pass it as a prop like the StaticRouter that's been in the v4 alphas.

@mjackson

mjackson commented Jan 6, 2017

Copy link
Copy Markdown

We folded everything in v4-alt into v4 this week, planning on shipping a v4 beta this next week with API that we've all agreed upon.

@lourd No worries, you'll have a <Router history> prop :)

@KaboomFox

KaboomFox commented Jan 21, 2017 •

Copy link
Copy Markdown

Any plans to create an alpha version of react-router-redux with v4 support?

@timdorr

timdorr commented Jan 21, 2017

Copy link
Copy Markdown
Contributor Author

Closing since v4 has been majorly refactored. I'll restart something else for this soon once I've had enough time to check out the new code (I'm behind!).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.