Skip to content

Commit

Permalink
[changed] handler keys to be optional
Browse files Browse the repository at this point in the history
gimme some of that sweet, sweet DOM diffing

closes remix-run#97
  • Loading branch information
ryanflorence committed Aug 26, 2014
1 parent 5ff2985 commit 2a85b74
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 22 deletions.
45 changes: 45 additions & 0 deletions UPGRADE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,51 @@ To see discussion around these API changes, please refer to the
[changelog](/CHANGELOG.md) and git log the commits to find the issues
they refer to.

0.5.x -> 0.6.x
--------------

If you have dynamic segments and are depending on `getInitialState`,
`componentWillMount`, or `componentDidMount` to fire between transitions
to the same route--like `users/123` and `users/456`, then you have two
options: add `addHandlerKey={true}` to your route and keep the previous
behavior (but lose out on performance), or implement
`componentWillReceiveProps`.

```js
// 0.5.x
<Route handler={User} path="/user/:userId"/>

// 0.6.x
<Route handler={User} path="/user/:userId" addHandlerKey={true} />

// 0.5.x
var User = React.createClass({
getInitialState: function() {
return {
user: getUser(this.props.params.userId);
}
}
});

// 0.6.x
var User = React.createClass({
getInitialState: function() {
return this.getState();{
},

componentWillReceiveProps: function(newProps) {
this.setState(this.getState(newProps));
},

getState: function(props) {
props = props || this.props;
return {
user: getUser(props.params.userId)
};
}
});
```
0.4.x -> 0.5.x
--------------
Expand Down
20 changes: 20 additions & 0 deletions docs/api/components/Route.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ inherit the path of their parent.

The component to be rendered when the route is active.

### `addHandlerKey`

Defaults to `false`.

If you have dynamic segments in your URL, a transition from `/users/123`
to `/users/456` does not call `getInitialState`, `componentWillMount` or
`componentWillUnmount`. If you are using those lifecycle hooks to fetch
data and set state, you will also need to implement
`componentWillReceiveProps` on your handler, just like any other
component with changing props. This way, you can leverage the
performance of the React DOM diff algorithm. Look at the `Contact`
handler in the `master-detail` example.

If you'd rather be lazy, set this to `true` and the router will add a
key to your route, causing all new DOM to be built, and then the life
cycle hooks will all be called.

You will want this to be `true` if you're doing animations with React's
TransitionGroup component.

### `preserveScrollPosition`

If `true`, the router will not scroll the window up when the route is
Expand Down
17 changes: 17 additions & 0 deletions docs/guides/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,22 @@ how you can turn this parameter into state on your component. Or for a
more basic approach, make an ajax call in `componentDidMount` with the
value.

Important Note About Dynamic Segments
-------------------------------------

If you have dynamic segments in your URL, a transition from `/users/123`
to `/users/456` does not call `getInitialState`, `componentWillMount` or
`componentWillUnmount`. If you are using those lifecycle hooks to fetch
data and set state, you will also need to implement
`componentWillReceiveProps` on your handler, just like any other
component whose props are changing. This way you can leverage the
performance of the React DOM diff algorithm. Look at the `Contact`
handler in the `master-detail` example.

If you'd rather be lazy, you can use the `addHandlerKey` option and set
it to `true` on your route to opt-out of the performance. See also
[Route][Route].

Links
-----

Expand All @@ -306,5 +322,6 @@ it has to offer. Check out the [API Docs][API] to learn about
redirecting transitions, query parameters and more.

[AsyncState]:../api/mixins/AsyncState.md
[Route]:../api/components/Route.md
[API]:../api/

2 changes: 1 addition & 1 deletion examples/animations/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var Image = React.createClass({
var routes = (
<Routes>
<Route handler={App}>
<Route name="image" path="/:service" handler={Image}/>
<Route name="image" path="/:service" handler={Image} addHandlerKey={true} />
</Route>
</Routes>
);
Expand Down
46 changes: 27 additions & 19 deletions examples/master-detail/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,17 @@ var Index = React.createClass({
});

var Contact = React.createClass({
getInitialState: function() {
getStateFromStore: function(props) {
props = props || this.props;
return {
contact: ContactStore.getContact(this.props.params.id)
contact: ContactStore.getContact(props.params.id)
};
},

getInitialState: function() {
return this.getStateFromStore();
},

componentDidMount: function() {
ContactStore.addChangeListener(this.updateContact);
},
Expand All @@ -144,13 +149,15 @@ var Contact = React.createClass({
ContactStore.removeChangeListener(this.updateContact);
},

componentWillReceiveProps: function(newProps) {
this.setState(this.getStateFromStore(newProps));
},

updateContact: function () {
if (!this.isMounted())
return;

this.setState({
contact: ContactStore.getContact(this.props.params.id)
});
this.setState(this.getStateFromStore())
},

destroy: function() {
Expand Down Expand Up @@ -204,20 +211,6 @@ var NotFound = React.createClass({
}
});

var routes = (
<Route handler={App}>
<DefaultRoute handler={Index}/>
<Route name="new" path="contact/new" handler={NewContact}/>
<Route name="not-found" path="contact/not-found" handler={NotFound}/>
<Route name="contact" path="contact/:id" handler={Contact}/>
</Route>
);

React.renderComponent(
<Routes children={routes}/>,
document.getElementById('example')
);

// Request utils.

function getJSON(url, cb) {
Expand Down Expand Up @@ -249,3 +242,18 @@ function deleteJSON(url, cb) {
req.open('DELETE', url);
req.send();
}

var routes = (
<Route handler={App}>
<DefaultRoute handler={Index}/>
<Route name="new" path="contact/new" handler={NewContact}/>
<Route name="not-found" path="contact/not-found" handler={NotFound}/>
<Route name="contact" path="contact/:id" handler={Contact}/>
</Route>
);

React.renderComponent(
<Routes children={routes}/>,
document.getElementById('example')
);

2 changes: 1 addition & 1 deletion examples/simple-master-detail/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var State = React.createClass({
var routes = (
<Routes>
<Route handler={App}>
<Route name="state" path="state/:abbr" handler={State}/>
<Route name="state" path="state/:abbr" addHandlerKey={true} handler={State}/>
</Route>
</Routes>
);
Expand Down
4 changes: 3 additions & 1 deletion modules/components/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,12 @@ function computeHandlerProps(matches, query) {
props = Route.getUnreservedProps(route.props);

props.ref = REF_NAME;
props.key = Path.injectParams(route.props.path, match.params);
props.params = match.params;
props.query = query;

if (route.props.addHandlerKey)
props.key = Path.injectParams(route.props.path, match.params);

if (childHandler) {
props.activeRouteHandler = childHandler;
} else {
Expand Down

0 comments on commit 2a85b74

Please sign in to comment.