From 4202fcd9c5cbfc94907ab9af5bc0bc51bbf14037 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 26 Oct 2014 23:52:37 -0700 Subject: [PATCH] Replace transferPropsTo with transferring props patterns --- docs/02.2-jsx-spread.md | 2 +- docs/05-reusable-components.md | 2 +- docs/06-transferring-props.md | 159 ++++++++++++++++++ docs/{06-forms.md => 07-forms.md} | 2 +- ...wser.md => 08-working-with-the-browser.md} | 0 ...-about-refs.md => 08.1-more-about-refs.md} | 0 ...tegration.md => 09-tooling-integration.md} | 0 docs/{09-addons.md => 10-addons.md} | 0 docs/{09.1-animation.md => 10.1-animation.md} | 0 ...ar.md => 10.2-form-input-binding-sugar.md} | 0 ...ion.md => 10.3-class-name-manipulation.md} | 0 ...{09.4-test-utils.md => 10.4-test-utils.md} | 0 ...with-props.md => 10.5-clone-with-props.md} | 0 docs/{09.6-update.md => 10.6-update.md} | 0 ...der-mixin.md => 10.7-pure-render-mixin.md} | 0 docs/{09.8-perf.md => 10.8-perf.md} | 0 docs/ref-02-component-api.md | 27 --- 17 files changed, 162 insertions(+), 30 deletions(-) create mode 100644 docs/06-transferring-props.md rename docs/{06-forms.md => 07-forms.md} (99%) rename docs/{07-working-with-the-browser.md => 08-working-with-the-browser.md} (100%) rename docs/{07.1-more-about-refs.md => 08.1-more-about-refs.md} (100%) rename docs/{08-tooling-integration.md => 09-tooling-integration.md} (100%) rename docs/{09-addons.md => 10-addons.md} (100%) rename docs/{09.1-animation.md => 10.1-animation.md} (100%) rename docs/{09.2-form-input-binding-sugar.md => 10.2-form-input-binding-sugar.md} (100%) rename docs/{09.3-class-name-manipulation.md => 10.3-class-name-manipulation.md} (100%) rename docs/{09.4-test-utils.md => 10.4-test-utils.md} (100%) rename docs/{09.5-clone-with-props.md => 10.5-clone-with-props.md} (100%) rename docs/{09.6-update.md => 10.6-update.md} (100%) rename docs/{09.7-pure-render-mixin.md => 10.7-pure-render-mixin.md} (100%) rename docs/{09.8-perf.md => 10.8-perf.md} (100%) diff --git a/docs/02.2-jsx-spread.md b/docs/02.2-jsx-spread.md index d07c4ed29..7a147584c 100644 --- a/docs/02.2-jsx-spread.md +++ b/docs/02.2-jsx-spread.md @@ -68,4 +68,4 @@ Merging two objects can be expressed as: > Note: > -> Activate experimental syntax by using the [JSX command-line tool](http://npmjs.org/package/react-tools) with the `--harmony` flag. +> Use the [JSX command-line tool](http://npmjs.org/package/react-tools) with the `--harmony` flag to activate the experimental ES7 syntax. diff --git a/docs/05-reusable-components.md b/docs/05-reusable-components.md index 7835c9a5a..5ad85a53e 100644 --- a/docs/05-reusable-components.md +++ b/docs/05-reusable-components.md @@ -3,7 +3,7 @@ id: reusable-components title: Reusable Components permalink: reusable-components.html prev: multiple-components.html -next: forms.html +next: transferring-props.html --- When designing interfaces, break down the common design elements (buttons, form fields, layout components, etc) into reusable components with well-defined interfaces. That way, the next time you need to build some UI you can write much less code, which means faster development time, fewer bugs, and fewer bytes down the wire. diff --git a/docs/06-transferring-props.md b/docs/06-transferring-props.md new file mode 100644 index 000000000..f93fbe2fe --- /dev/null +++ b/docs/06-transferring-props.md @@ -0,0 +1,159 @@ +--- +id: transferring-props +title: Transferring Props +permalink: transferring-props.html +prev: reusable-components.html +next: forms.html +--- + +It's a common pattern in React to wrap a component in an abstraction. The outer component exposes a simple property to do something that might have more complex implementation details. + +You can use [JSX spread attributes](/react/docs/jsx-spread.html) to merge the old props with additional values: + +```javascript +return ; +``` + +If you don't use JSX, you can use any object helper such as ES6 `Object.assign` or Underscore `_.extend`: + +```javascript +return Component(Object.assign({}, this.props, { more: 'values' })); +``` + +The rest of this tutorial explains best practices. It uses JSX and experimental ES7 syntax. + +## Manual Transfer + +Most of the time you should explicitly pass the properties down. That ensures that you only exposes a subset of the inner API, one that you know will work. + +```javascript +var FancyCheckbox = React.createClass({ + render: function() { + var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked'; + return ( +
+ {this.props.children} +
+ ); + } +}); +React.renderComponent( + + Hello world! + , + document.body +); +``` + +But what about the `name` prop? Or the `title` prop? Or `onMouseOver`? + +## Transferring with `...` in JSX + +Sometimes it's fragile and tedious to pass every property along. In that case you can use [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) with rest properties to extract a set of unknown properties. + +List out all the properties that you would like to consume, followed by `...other`. + +```javascript +var { checked, ...other } = this.props; +``` + +This ensures that you pass down all the props EXCEPT the ones you're consuming yourself. + +```javascript +var FancyCheckbox = React.createClass({ + render: function() { + var { checked, ...other } = this.props; + var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked'; + // `other` contains { onClick: console.log } but not the checked property + return ( +
+ ); + } +}); +React.renderComponent( + + Hello world! + , + document.body +); +``` + +> NOTE: +> +> In the example above, the `checked` prop is also a valid DOM attribute. If you didn't use destructuring in this way you might inadvertently pass it along. + +Always use the destructuring pattern when transferring unknown `other` props. + +```javascript +var FancyCheckbox = React.createClass({ + render: function() { + var fancyClass = this.props.checked ? 'FancyChecked' : 'FancyUnchecked'; + // ANTI-PATTERN: `checked` would be passed down to the inner component + return ( +
+ ); + } +}); +``` + +## Consuming and Transferring the Same Prop + +If your component wants to consume a property but also pass it along, you can repass it explicitly `checked={checked}`. This is preferable to passing the full `this.props` object since it's easier to refactor and lint. + +```javascript +var FancyCheckbox = React.createClass({ + render: function() { + var { checked, title, ...other } = this.props; + var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked'; + var fancyTitle = checked ? 'X ' + title : 'O ' + title; + return ( + + ); + } +}); +``` + +> NOTE: +> +> Order matters. By putting the `{...other}` before your JSX props you ensure that the consumer of your component can't override them. In the example above we have guaranteed that the input will be of type `"checkbox"`. + +## Rest and Spread Properties `...` + +Rest properties allow you to extract the remaining properties from an object into a new object. It excludes every other property listed in the destructuring pattern. + +This is an experimental implementation of an [ES7 proposal](https://github.com/sebmarkbage/ecmascript-rest-spread). + +```javascript +var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; +x; // 1 +y; // 2 +z; // { a: 3, b: 4 } +``` + +> Note: +> +> Use the [JSX command-line tool](http://npmjs.org/package/react-tools) with the `--harmony` flag to activate the experimental ES7 syntax. + +## Transferring with Underscore + +If you don't use JSX, you can use a library to achieve the same pattern. Underscore supports `_.omit` to filter out properties and `_.extend` to copy properties onto a new object. + +```javascript +var FancyCheckbox = React.createClass({ + render: function() { + var checked = this.props.checked; + var other = _.omit(this.props, 'checked'); + var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked'; + return ( + React.DOM.div(_.extend({}, other, { className: fancyClass })) + ); + } +}); +``` diff --git a/docs/06-forms.md b/docs/07-forms.md similarity index 99% rename from docs/06-forms.md rename to docs/07-forms.md index 1dad3ea49..475c9183e 100644 --- a/docs/06-forms.md +++ b/docs/07-forms.md @@ -2,7 +2,7 @@ id: forms title: Forms permalink: forms.html -prev: reusable-components.html +prev: transferring-props.html next: working-with-the-browser.html --- diff --git a/docs/07-working-with-the-browser.md b/docs/08-working-with-the-browser.md similarity index 100% rename from docs/07-working-with-the-browser.md rename to docs/08-working-with-the-browser.md diff --git a/docs/07.1-more-about-refs.md b/docs/08.1-more-about-refs.md similarity index 100% rename from docs/07.1-more-about-refs.md rename to docs/08.1-more-about-refs.md diff --git a/docs/08-tooling-integration.md b/docs/09-tooling-integration.md similarity index 100% rename from docs/08-tooling-integration.md rename to docs/09-tooling-integration.md diff --git a/docs/09-addons.md b/docs/10-addons.md similarity index 100% rename from docs/09-addons.md rename to docs/10-addons.md diff --git a/docs/09.1-animation.md b/docs/10.1-animation.md similarity index 100% rename from docs/09.1-animation.md rename to docs/10.1-animation.md diff --git a/docs/09.2-form-input-binding-sugar.md b/docs/10.2-form-input-binding-sugar.md similarity index 100% rename from docs/09.2-form-input-binding-sugar.md rename to docs/10.2-form-input-binding-sugar.md diff --git a/docs/09.3-class-name-manipulation.md b/docs/10.3-class-name-manipulation.md similarity index 100% rename from docs/09.3-class-name-manipulation.md rename to docs/10.3-class-name-manipulation.md diff --git a/docs/09.4-test-utils.md b/docs/10.4-test-utils.md similarity index 100% rename from docs/09.4-test-utils.md rename to docs/10.4-test-utils.md diff --git a/docs/09.5-clone-with-props.md b/docs/10.5-clone-with-props.md similarity index 100% rename from docs/09.5-clone-with-props.md rename to docs/10.5-clone-with-props.md diff --git a/docs/09.6-update.md b/docs/10.6-update.md similarity index 100% rename from docs/09.6-update.md rename to docs/10.6-update.md diff --git a/docs/09.7-pure-render-mixin.md b/docs/10.7-pure-render-mixin.md similarity index 100% rename from docs/09.7-pure-render-mixin.md rename to docs/10.7-pure-render-mixin.md diff --git a/docs/09.8-perf.md b/docs/10.8-perf.md similarity index 100% rename from docs/09.8-perf.md rename to docs/10.8-perf.md diff --git a/docs/ref-02-component-api.md b/docs/ref-02-component-api.md index 35d01fa7c..2deeef703 100644 --- a/docs/ref-02-component-api.md +++ b/docs/ref-02-component-api.md @@ -70,33 +70,6 @@ bool isMounted() `isMounted()` returns true if the component is rendered into the DOM, false otherwise. You can use this method to guard asynchronous calls to `setState()` or `forceUpdate()`. -### transferPropsTo - -```javascript -ReactComponent transferPropsTo(ReactComponent targetComponent) -``` - -Transfer properties from this component to a target component that have not already been set on the target component. After the props are updated, `targetComponent` is returned as a convenience. This function is useful when creating simple HTML-like components: - -```javascript -var Avatar = React.createClass({ - render: function() { - return this.transferPropsTo( - - ); - } -}); - -// -``` - -Properties that are specified directly on the target component instance (such as `src` and `userId` in the above example) will not be overwritten by `transferPropsTo`. - -> Note: -> -> Use `transferPropsTo` with caution; it encourages tight coupling and makes it easy to accidentally introduce implicit dependencies between components. When in doubt, it's safer to explicitly copy the properties that you need onto the child component. - - ### setProps ```javascript