diff --git a/UPGRADE_GUIDE.md b/UPGRADE_GUIDE.md
index 0c00a6325d..6d679ff66b 100644
--- a/UPGRADE_GUIDE.md
+++ b/UPGRADE_GUIDE.md
@@ -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
+
+
+// 0.6.x
+
+
+// 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
--------------
diff --git a/docs/api/components/Route.md b/docs/api/components/Route.md
index acc1d35f91..1183f8dc5d 100644
--- a/docs/api/components/Route.md
+++ b/docs/api/components/Route.md
@@ -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
diff --git a/docs/guides/overview.md b/docs/guides/overview.md
index ee9edba770..7068e8925a 100644
--- a/docs/guides/overview.md
+++ b/docs/guides/overview.md
@@ -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
-----
@@ -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/
diff --git a/examples/animations/app.js b/examples/animations/app.js
index 031ec7121f..4ecf244722 100644
--- a/examples/animations/app.js
+++ b/examples/animations/app.js
@@ -36,7 +36,7 @@ var Image = React.createClass({
var routes = (
-
+
);
diff --git a/examples/master-detail/app.js b/examples/master-detail/app.js
index 272e489b10..78ce16eba2 100644
--- a/examples/master-detail/app.js
+++ b/examples/master-detail/app.js
@@ -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);
},
@@ -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() {
@@ -204,20 +211,6 @@ var NotFound = React.createClass({
}
});
-var routes = (
-
-
-
-
-
-
-);
-
-React.renderComponent(
- ,
- document.getElementById('example')
-);
-
// Request utils.
function getJSON(url, cb) {
@@ -249,3 +242,18 @@ function deleteJSON(url, cb) {
req.open('DELETE', url);
req.send();
}
+
+var routes = (
+
+
+
+
+
+
+);
+
+React.renderComponent(
+ ,
+ document.getElementById('example')
+);
+
diff --git a/examples/simple-master-detail/app.js b/examples/simple-master-detail/app.js
index 08b20b53bf..b85fe6e620 100644
--- a/examples/simple-master-detail/app.js
+++ b/examples/simple-master-detail/app.js
@@ -53,7 +53,7 @@ var State = React.createClass({
var routes = (
-
+
);
diff --git a/modules/components/Routes.js b/modules/components/Routes.js
index 7d87437e78..23cf49d2d5 100644
--- a/modules/components/Routes.js
+++ b/modules/components/Routes.js
@@ -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 {