Skip to content

Documentation for UsageWithReactRouter.md #1929

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

Merged
merged 17 commits into from
Sep 4, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 156 additions & 2 deletions docs/advanced/UsageWithReactRouter.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,158 @@
# Usage with React Router

Sorry, but we're still writing this doc.
Stay tuned, it will appear in a day or two.
So you want to do routing with your Redux app. You can use it with the react-router library. Redux will be the source of truth for your data and react-router will be the source of truth for your URL. In most of the cases, **it is fine** to have them separate unless if you need to time travel and rewind actions that triggers the change URL.

## Redux-router, react-router-redux, react-router
Before starting, let's clarify the different routing libraries.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a section on React Router first and make it clear you can use it directly with Redux just fine. Many people don't realize this.

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact I’d like to completely deemphasize these other libraries here and maybe add them as a footnote. Let’s focus on RR instead.

### Redux-router
Redux-router is an **experimental** library, it lets you keep entirely the state of your url inside your redux store. It has the same API with React Router API but has a smaller community support than react-router.

### React-router-redux
react-router-redux creates binding between your redux app and react-router and it keeps them in sync. Without this binding, you will not be able to rewind the actions with Time Travel. Unless you need this, React-router and Redux can operates completely apart.


## Installing react-router
Copy link
Contributor

@gaearon gaearon Sep 3, 2016

Choose a reason for hiding this comment

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

Let's always refer to it as React Router in writing, not react-router (unless we are talking about npm package).

react-router is available on npm :

`npm install --save react-router`

## Configuring the fallback url
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nit: let’s style header text as Pascal Case.
e..g Configuring the Fallback URL


Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make it a header just like Express and WebpackDevServer below

Before implementing react-router, we need to configure our back-end. Indeed, our back-end is not currently aware of the declared routes in react-router. If you refresh your page or try to access directly an URL declared in react-router without having configured your back-end, you will get an 404. You will be first requesting a url that the back-end is not aware of, instead of asking to react-router. You need to configure a fallback URL, to serve index.html on an unknown URL so that in the front-end, react-router can handle the request.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let’s replace “back-end” with “development server” here.
Also let’s note that Create React App (which we use for examples now) already does this automatically.


### Configuring express.js
If you are serving your index.html from express.js :
``` js
app.get('/*', (req,res) => {
res.sendfile(path.join(__dirname, 'index.html'))
})
```

### Configuring webpack-dev-server
If you are serving your index.html from webpack-dev-server:
You can add to your webpack.config.dev.js :
```
devServer: {
historyApiFallback: true,
}
```

## Connecting the router with Redux App

Along this chapter, we will be using the [Todos](https://github.com/reactjs/redux/tree/master/examples/todos) example. We recommend you to clone it while reading this chapter.

The <Router /> component has to be a children of `<Provider/>` so the `<Router />` has access to the global `store`. `<Provider/>` is the higher-order component provided by react-redux that lets you bind Redux to React (see [Usage with React](../basics/UsageWithReact.md)).
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not quite correct. We put <Router /> into <Provider> so that route handlers (including our App) will get access to the store. Router component itself doesn’t care.

Copy link
Contributor

@gaearon gaearon Sep 3, 2016

Choose a reason for hiding this comment

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

Also please don’t forget backticks: <Router />, not < Router />


The `<Route>` component lets you define a component to be loaded whenever an url entered match with the property `path`. We added the optional `(:filter)` parameter so it will renders the `<App />` component if the url match '/'.
Copy link
Contributor

Choose a reason for hiding this comment

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

so it will renders => so that it renders


Passing the `browserHistory` is necessary if you want to remove the hash from URL (e.g : `http://localhost:3000/#/?_k=4sbb0i`). Unless you are targeting old browsers like IE9, you can always use `browserHistory`.

``` js
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router';
import App from './App';

const Root = ({ store }) => (
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/(:filter)" component={App} />
</Router>
</Provider>
);

Root.propTypes = {
store: PropTypes.object.isRequired,
};

export default Root;
```

## Navigating with react-router

react-router comes with a [<Link/>](https://github.com/reactjs/react-router/blob/master/docs/API.md#link) component that let you navigate around your application. We can use it in our example and change our container `<FilterLink />` component so we can change the URL using `<FilterLink />. The `activeStyle={}` property lets you apply a style on the active state.


#### `containers/FilterLink.js`
```js
import React from 'react';
import { Link } from 'react-router';

const FilterLink = ({ filter, children }) => (
<Link
to={filter === 'all' ? '' : filter}
activeStyle={{
textDecoration: 'none',
color: 'black'
}}
>
{children}
</Link>
);

export default FilterLink;
```

#### `containers/Footer`
Copy link
Contributor

Choose a reason for hiding this comment

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

.js

```js
import React from 'react'
import FilterLink from '../containers/FilterLink'

const Footer = () => (
<p>
Show:
{" "}
<FilterLink filter="all">
All
</FilterLink>
{", "}
<FilterLink filter="active">
Active
</FilterLink>
{", "}
<FilterLink filter="completed">
Completed
</FilterLink>
</p>
)

export default Footer
```

Now if you click on `<FilterLink/>` you will see that your URL will change from `'/complete'`, `'/active'`, `'/'`. Even if you are going back with your browser, it will use your browser's history and effectively go to your previous URL.
Copy link
Contributor

Choose a reason for hiding this comment

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

please put a space before />


## Read the URL and applying it to the Redux Store

Currently, the todos list are not filtered even after the URL changed. This is because we are filtering from `<VisibleTodoList />`'s `mapStateToProps()` is still binded to the `state` and not to the URL. `mapStateToProps` has an optional second argument `ownProps` that is an object with every props passed to `<VisibleTodoList />`
Copy link
Contributor

Choose a reason for hiding this comment

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

either "todos are not filtered" or "todo list is not filtered"

#### `components/App.js`
```js
const mapStateToProps = (state, ownProps) => {
return {
todos: getVisibleTodos(state.todos, ownProps.filter) // previously was getVisibleTodos(state.todos, state.visibilityFilter)
}
}
```

Right now we are not passing anything to `<App />` so `ownProps` is an empty object. To filter our todos according to the URL, we want to pass the URL params to `<VisibleTodoList />`.

When previously we wrote: `<Route path="/(:filter)" component={App} />`, it made available inside `App` a `params` property.

`params` property is an object with every param specified in the url. *e.g : `params` will be equal to `{ filter: 'completed' }` if we are navigating to `localhost:3000/completed`. We can now read the URL from `<App />`.*

Note that we are using [ES6 destructuring](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) on the properties to pass in `params` to `<VisibleTodoList/>`.

#### `components/App.js`
```js
const App = ({ params }) => {
return (
<div>
<AddTodo />
<VisibleTodoList
filter={params.filter || 'all'}
/>
<Footer />
</div>
)
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

We should add "Next Steps" here linking to react router docs at the very least so people can learn more about its API.