From 2a85b74aa098aa67e014bea14b16acf1490429a4 Mon Sep 17 00:00:00 2001 From: Ryan Florence Date: Mon, 25 Aug 2014 22:05:29 -0600 Subject: [PATCH] [changed] handler keys to be optional gimme some of that sweet, sweet DOM diffing closes #97 --- UPGRADE_GUIDE.md | 45 +++++++++++++++++++++++++++ docs/api/components/Route.md | 20 ++++++++++++ docs/guides/overview.md | 17 ++++++++++ examples/animations/app.js | 2 +- examples/master-detail/app.js | 46 ++++++++++++++++------------ examples/simple-master-detail/app.js | 2 +- modules/components/Routes.js | 4 ++- 7 files changed, 114 insertions(+), 22 deletions(-) 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 {