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 #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

5 comments on commit 2a85b74

@mjackson
Copy link
Member

Choose a reason for hiding this comment

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

This all looks great. Just wondering about the addHandlerKey name. A few alternatives:

  • useKey
  • useAutoKey
  • useDynamicKey
  • autoKey

Probably my fave is autoKey. @rpflorence Thoughts?

@ryanflorence
Copy link
Member Author

Choose a reason for hiding this comment

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

I went with addHandlerKey because if you read it without knowing anything about the router you at least know its going to add a key to the handler.

I don't have strong feelings about the name of this option though, so whatever you want is fine with me.

@ryanflorence
Copy link
Member Author

Choose a reason for hiding this comment

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

Also, why are you commenting on commits instead of the pull request? Weirdo.

@mjackson
Copy link
Member

Choose a reason for hiding this comment

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

Also, why are you commenting on commits instead of the pull request? Weirdo.

Good point. Why does GitHub even have comments down here?

@ryanflorence
Copy link
Member Author

Choose a reason for hiding this comment

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

its nice when the commit has already been merged into master

Please sign in to comment.