Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Usage with React Router #637

Closed
gaearon opened this issue Aug 27, 2015 · 61 comments
Closed

Docs: Usage with React Router #637

gaearon opened this issue Aug 27, 2015 · 61 comments
Labels

Comments

@gaearon
Copy link
Contributor

gaearon commented Aug 27, 2015

People constantly get the impression we don't support React Router, or that you need something special like redux-react-router for it to work, or even that it doesn't work until React 0.14.

You can use Redux with React Router 0.13 or 1.0 as is today.
(And that was true since Redux's initial release by the way.)

Some features like time travel will need to wait for some support on RR side, but this is irrelevant to the main issue. People get confused thinking they can't use routing today, which is just wrong.

The real world example included in this repo uses React Router. All you need is to wrap <Router> into <Provider>, just like you'd wrap your top-level component in a router-less app.

If you want to transition from action creators, pass router instance as a parameter to action creators, and call its methods when you like. If you want to read router state from Redux store, fire an action on route change and write a reducer to handle it. That's it!

react-redux-router is an experiment in trying to come up with a more natural API where you'd just dispatch actions and read state from store which automagically is connected to the router instance, but you don't have to use it, or to wait for it to become stable! It's an experiment only.

We need this in docs..

@gaearon gaearon added the docs label Aug 27, 2015
@hedgerh
Copy link
Contributor

hedgerh commented Aug 27, 2015

I had originally wrote the server side rendering example to use react router in a universal context. It was suggested that usage with react router could become a document of its own. Perhaps we could add a section to the documentation?

@hedgerh
Copy link
Contributor

hedgerh commented Aug 27, 2015

Also, how do you feel about adding an example that shows react router being universally used?

@timdorr
Copy link
Member

timdorr commented Aug 28, 2015

I think modifying the real-world example to be universal would be a good idea. That way you don't have to build anything from scratch.

@kolodny
Copy link
Contributor

kolodny commented Aug 28, 2015

If you want to transition from action creators, pass router instance as a parameter to action creators

Just wanted to clarify, this only works in 0.13 as 1.0beta doesn't have a createRouter concept (yet?), correct?

@timdorr
Copy link
Member

timdorr commented Aug 28, 2015

You can do it with 1.0.0-beta3 from within your React components.

class Thing extends Component {
  static contextTypes = {
    router: PropTypes.object
  }

  handleThing() {
    this.props.actionCreator(this.context.router);
  }
}

@kolodny
Copy link
Contributor

kolodny commented Aug 28, 2015

@timdorr Is it safe to use this.context ? I was under the impression that it wasn't meant to be used externally.

@timdorr
Copy link
Member

timdorr commented Aug 28, 2015

Yes, it's just undocumented, not unsafe. It's getting changed slightly in 0.14, but not in a way that would break this. I imagine it will be documented at some point soon.

@kolodny
Copy link
Contributor

kolodny commented Aug 30, 2015

@timdorr Does that also mean it's possible to transition to a different url from an action creator in 1.0.0-beta3 ?

@timdorr
Copy link
Member

timdorr commented Aug 30, 2015

Yes, if you pass along the router instance to the action creator, you can do whatever you want with it, including transitions.

@kolodny
Copy link
Contributor

kolodny commented Aug 30, 2015

I threw together this:



const loginProps = {
  handleLogin: ({email, password}) => store.dispatch(userLogin({email, password})),
};



const routes = (
  <Route path="/" handler={App}>
    <Route path="login" handler={wrapper(Login, loginProps)} />
    <Route handler={authSection}>
      <Route path="" handler={Index} />
      <Route path="users" handler={wrapper(Users, (() => store.dispatch(getUsers())))} />
      <Route path="logout" handler={wrapper(Login, (() => store.dispatch(userLogout())))} />
    </Route>
  </Route>
);

const router = createRouter({
  location: HistoryLocation,
  routes,
});

store.dispatch(receiveRouter({router}));

@gyzerok
Copy link

gyzerok commented Sep 1, 2015

Warning: Failed Context Types: Required context `store` was not specified in `SmartComponent(TodoApp)`. Check the render method of `Router`.

What's may be wrong?

P.S.: RR 1.0.0-beta3

@gaearon
Copy link
Contributor Author

gaearon commented Sep 1, 2015

@gyzerok Make sure you wrap the whole () => <Router>stuff</Router> into <Provider>, just like real-world example does it.

@gyzerok
Copy link

gyzerok commented Sep 1, 2015

@gaearon yes, ofc. I wrap it not in Provider but in my own component which is mostly copy-paste of your Provider. The difference is I do not pass store to it, but create store inside of it.

@gaearon
Copy link
Contributor Author

gaearon commented Sep 1, 2015

@gyzerok It's hard to say what's wrong without seeing the code. (And please file a separate issue, react-redux repo is a good place.)

@wtfil
Copy link

wtfil commented Sep 7, 2015

Thx for real-wolrd example! But how to deal with AsyncProps? Seems like double context manipulation won't work together.

import React from 'react';                                                                                                                                                                                         
import {createStore} from 'redux';                                                                                                                                                                                 
import {Provider} from 'react-redux';                                                                                                                                                                              
import {Router, Route} from 'react-router';                                                                                                                                                                        
import BrowserHistory from 'react-router/lib/BrowserHistory';                                                                                                                                                      
import AsyncProps from 'react-router/lib/experimental/AsyncProps';                                                                                                                                                 

import App from './containers/App';                                                                                                                                                                                
import reducers from './reducers';                                                                                                                                                                                 

const store = createStoreWithMiddleware(reducers);                                                                                                                                                                 
const history = new BrowserHistory();                                                                                                                                                                               

React.render(                                                                                                                                                                                                       
    <Provider store={store}>                                                                                                                                                                                        
        {() =>                                                                                                                                                                                                      
            <Router history={history} createElement={AsyncProps.createElement}>                                                                                                                                     
                <Route component={AsyncProps}>                                                                                                                                                                      
                    <Route path="/" component={App} />                                                                                                                                                              
                </Route>                                                                                                                                                                                            
            </Router>                                                                                                                                                                                               
        }                                                                                                                                                                                                           
    </Provider>,                                                                                                                                                                                                    
    document.body                                                                                                                                                                                                  
);

and App.js

import React from 'react';                                                                                                                                                                                         
import {connect} from 'react-redux';                                                                                                                                                                               

let App = React.createClass({                                                                                                                                                                                      
    statics: {                                                                                                                                                                                                     
        loadProps(params, cb) {                                                                                                                                                                                    
            // have to call this with AsyncProps                                                                                                                                                                   
        }                                                                                                                                                                                                          
    },                                                                                                                                                                                                             
    displayName: 'App',                                                                                                                                                                                            

    render() {                                                                                                                                                                                                     
        return <div children="this is app" />                                                                                                                                                                      
    }                                                                                                                                                                                                              
});                                                                                                                                                                                                                

export default connect(state => state)(App); 

It will work without connect wrapper, but there will no redux as well. Has anyone faced this problem?

Or may be any other way to pause navigation until data loaded?

@gaearon
Copy link
Contributor Author

gaearon commented Sep 7, 2015

Statics are just statics. You can put them on anything, including connect() result.

let App = React.createClass({                                                                                                                                                                                      
    displayName: 'App',                                                                                                                                                                                            

    render() {                                                                                                                                                                                                     
        return <div children="this is app" />                                                                                                                                                                      
    }                                                                                                                                                                                                              
});                                                                                                                                                                                                                

App = connect(state => state)(App); 

App.loadProps = function loadProps(params, cb) {                                                                                                                                                                                    
  // have to call this with AsyncProps                                                                                                                                                                   
}                                                                                                                                                                                                          

export default App; 

@wtfil
Copy link

wtfil commented Sep 7, 2015

@gaearon sorry, example is not full. I tried extend connect result with missing static props, but real problem with context. Give me some time, I will push whole example into separate repo

@grabbou
Copy link

grabbou commented Sep 11, 2015

Another thing I'm currently investigating is storing params in a redux store in a clear, unobstrusive way. After trying out few approaches, I ended up writing:

<Route
  component={OrderDetails}
  path='/orders/:orderId'
  onEnter={({params}) => store.dispatch(setCurrentOrder(params.orderId))} 
/>

so it's possible to refer to params from selectors, like below:

export const OrderDetails = state => {
  const {order} = state;
  return {
    order: order.details.get(order.currentOrderId),
    orderId: order.currentOrderId,
    isLoading: order.isLoadingDetails,
    error: order.detailsLoadingError
  };
};

It will probably change once more stable react-redux-router is released.

@gaearon
Copy link
Contributor Author

gaearon commented Sep 12, 2015

Good news: React Router 1.0 RC now exposes the hooks we need.
Check out https://github.com/acdlite/redux-react-router and let us know whether you like it now!

@wtfil
Copy link

wtfil commented Sep 12, 2015

@gaearon i found the issue with react-router and experimental AsyncProps. Updating the react solves the problem

@gaearon
Copy link
Contributor Author

gaearon commented Sep 12, 2015

@wtfil Good to know!

@cappslock
Copy link

When integrating with react-router, I understand from this thread that we can pass the router instance to an action creator and call methods on it. However, I also understand that action creators are intended not to have side effects in their purest form. The only case I see where action creators are allowed to have side effects is when they are asynchronous actions handled by middleware. Is the expectation then that action creators which perform transitions should be asynchronous, rather than pure functions?

@gaearon
Copy link
Contributor Author

gaearon commented Sep 25, 2015

Is the expectation then that action creators which perform transitions should be asynchronous, rather than pure functions?

Action creators are allowed to have side effects. It's best to avoid them when possible but of course at some point you need them, and since reducers are pure in Redux, and we don't have an explicit effect mechanism like in Elm (see #569 for discussion), action creators is the place to put them.

Check out redux-router. It works on top of React Router, but lets you dispatch actions and takes care of syncing the router.

@eriknyk
Copy link
Contributor

eriknyk commented Oct 9, 2015

waiting for React Router and Redux Router both hit 1.0...

@gaearon
Copy link
Contributor Author

gaearon commented Oct 9, 2015

Yep. We'll add a recipe after this happens.

@gaearon
Copy link
Contributor Author

gaearon commented Nov 9, 2015

1.0 is out.

It's time to:

  • Port Routing example to using it
  • Add “Usage with Router” recipe based on real-world example

@kolodny
Copy link
Contributor

kolodny commented Nov 9, 2015

👏

@sjmueller
Copy link

@gaearon can you reference this issue when the Usage with Router example is in a PR? Many folks I know (myself included) are looking for clarification on how these two play well together.

@gaearon
Copy link
Contributor Author

gaearon commented Nov 10, 2015

Yes, sure. This is when the issue will get closed. :-)

@fhelwanger
Copy link

Maybe redux-simple-router should be considered now?

@gaearon gaearon changed the title Clarify we work with React Router just fine Docs: Usage with React Router Nov 16, 2015
@eriknyk
Copy link
Contributor

eriknyk commented Nov 18, 2015

redux-simple-router +1

@eriknyk
Copy link
Contributor

eriknyk commented Nov 18, 2015

I just converted the universal example + react-router (+redux-simple-router)
https://github.com/eriknyk/redux-universal-app

@ionutzp
Copy link

ionutzp commented Dec 7, 2015

hi all, what's the conclusion of this discussion? i see the docs are not updated for usage with react-router
cc @gaearon

@anianj
Copy link

anianj commented Jan 15, 2016

@gaearon after i bound my react application to redux, i just use state to control the display/hidden of my components. So I don't think the original "router" role like RR fits to my application now.
The only thing i think the "new router" need to do is map the url to state(via actions?) and remap the state back to url too.
If we let url decide how the application component(maybe some of them) be displayed, that means we have two sources of state, one is the url, the other one is redux's store, that will make things harder...
What do you say about this? should we just let the address bar to be another of component of our application.

Thanks

@gaearon
Copy link
Contributor Author

gaearon commented Feb 5, 2016

I officially commit to writing this doc after we ship reactjs/react-router-redux#259. This will be the blessed way of binding React Router and Redux. In the doc, we will first show how to use them without that package, and gradually introduce two conveniences that the package affords: middleware and moving routing source of truth into the store. Both are optional so we will make sure to explain in which case you want to opt-in to using them, and what they give you over vanilla RR.

@pedrottimark
Copy link
Contributor

Here is a thought to consider: if anything you explain about middleware related to routing and history could also become a specific chunk of realistic application within the general explanation of http://rackt.org/redux/docs/advanced/Middleware.html (for example, in the examples at the end)

@benwiley4000
Copy link

@gaearon any progress on the React Router doc / next steps? I'm reading through the Redux documentation and loving it, but bummed out by those fake links :(

@knowbody
Copy link
Contributor

knowbody commented Apr 6, 2016

I only just started rewriting react router docs in my personal repo and I'm planning to have the redux section there as well. I might have something done soon, depends on my free time. I will keep you updated. https://github.com/knowbody/react-router-docs

@gaearon
Copy link
Contributor Author

gaearon commented Apr 6, 2016

To be fair you don't need anything on Redux side to get react router working. But yeah we need to do this.

@gaearon
Copy link
Contributor Author

gaearon commented May 9, 2016

Heads up: React Router 3.0 will work better with React Redux connect() optimizations, and new withRouter() HOC means you don’t need to use context directly.

https://twitter.com/dan_abramov/status/729768048417251328

@chipilov
Copy link

chipilov commented Jun 3, 2016

@gaearon, @timdorr could you clarify the trade-offs between passing a router instance as an argument to action creators and directly importing browserHistory in the action creators (as suggested here https://github.com/reactjs/react-router/blob/master/docs/guides/NavigatingOutsideOfComponents.md?

@timdorr
Copy link
Member

timdorr commented Jun 3, 2016

router just wraps your history instance with some extra goodies on it, but it's the same push and replace methods between the two instances.

@chipilov
Copy link

chipilov commented Jun 6, 2016

Thanks, @timdorr.

I guess the question then becomes why do we need the withRouter() composition if router basically wraps the history singleton (it's a singleton, right)?

Is it to allow looser coupling between a component and a history instance (i.e. to prevent the component from accessing directly a singleton object)? If so, wouldn't the same logic apply when accessing the history instance from an action creator?

@timdorr
Copy link
Member

timdorr commented Jun 6, 2016

Yes, and if you provide your own history instance and don't want to (or can't) make your own singleton module (which can be confusing if you're not super-familiar with JS module systems). If you want to do that yourself, you're more than welcome to follow our pattern.

@patrickheeney
Copy link

patrickheeney commented Jul 3, 2016

I am not sure if it would be worth-while to document withRouter on how it can be used for multiple higher order components. I am still trying to figure out the best way to avoid this:
connect(mapStateToProps, mapDispatchToProps)(withRouter(withAnalytics(withLanguage(TestForm))));.

I could also use something like compose?

const enhance = compose(
  connect(mapStateToProps, mapDispatchToProps),
  withRouter,
  withAnalytics,
  withLanguage
);

export default enhance(TestForm);

However, my use case the context is going to have logged in user, current language, theme information, and analytics which makes this challenging with lots of context and a lot of connected components.

Another idea is to duplicate withRouter and connect logic under one context namespace: withAppContext() => props.app = { user, lang, theme, analytics, router, connect? }?

Would this be beneficial for the documentation or an example withRouter usage with connect?

@likeabbas
Copy link

likeabbas commented Aug 31, 2016

@gaearon are there any updates for this now that React Router 3.0.0 and your new Egghead videos have been out for a while, and this thread has been opened for a year?

@timdorr
Copy link
Member

timdorr commented Sep 4, 2016

Done in #1929

@timdorr timdorr closed this as completed Sep 4, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests