diff --git a/docs/docs/07-working-with-the-browser.md b/docs/docs/07-working-with-the-browser.md index c654f9a965d..0ea557d480b 100644 --- a/docs/docs/07-working-with-the-browser.md +++ b/docs/docs/07-working-with-the-browser.md @@ -10,7 +10,7 @@ next: more-about-refs.html React provides powerful abstractions that free you from touching the DOM directly in most cases, but sometimes you simply need to access the underlying API, perhaps to work with a third-party library or existing code. -## The Mock DOM +## The Virtual DOM React is so fast because it never talks to the DOM directly. React maintains a fast in-memory representation of the DOM. `render()` methods return a *description* of the DOM, and React can diff this description with the in-memory representation to compute the fastest way to update the browser. diff --git a/docs/docs/08-tooling-integration.md b/docs/docs/08-tooling-integration.md index 83e1adc40bc..b032e9ccce1 100644 --- a/docs/docs/08-tooling-integration.md +++ b/docs/docs/08-tooling-integration.md @@ -55,7 +55,3 @@ The open-source community has built tools that integrate JSX with several build that support `*.tmLanguage`. * Linting provides accurate line numbers after compiling without sourcemaps. * Elements use standard scoping so linters can find usage of out-of-scope components. - -## React Page - -To get started on a new project, you can use [react-page](https://github.com/facebook/react-page/), a complete React project creator. It supports both server-side and client-side rendering, source transform and packaging JSX files using CommonJS modules, and instant reload. diff --git a/docs/docs/09.1-animation.md b/docs/docs/09.1-animation.md index fdcc40ae74b..c3d6c23f5d5 100644 --- a/docs/docs/09.1-animation.md +++ b/docs/docs/09.1-animation.md @@ -35,7 +35,7 @@ var TodoList = React.createClass({ render: function() { var items = this.state.items.map(function(item, i) { return ( -
+
{item}
); diff --git a/docs/docs/10-examples.md b/docs/docs/10-examples.md index abf2577d2f6..3d9995362f2 100644 --- a/docs/docs/10-examples.md +++ b/docs/docs/10-examples.md @@ -17,6 +17,5 @@ prev: class-name-manipulation.html * We've included [a step-by-step comment box tutorial](/react/docs/tutorial.html). * [The React starter kit](/react/downloads.html) includes several examples which you can [view online in our GitHub repository](https://github.com/facebook/react/tree/master/examples/). -* [React Page](https://github.com/facebook/react-page) is a simple React project creator to get you up-and-running quickly with React. It supports both server-side and client-side rendering, source transform and packaging JSX files using CommonJS modules, and instant reload. * [React one-hour email](https://github.com/petehunt/react-one-hour-email/commits/master) goes step-by-step from a static HTML mock to an interactive email reader (written in just one hour!) * [Rendr + React app template](https://github.com/petehunt/rendr-react-template/) demonstrates how to use React's server rendering capabilities. diff --git a/docs/docs/ref-01-top-level-api.md b/docs/docs/ref-01-top-level-api.md index 4d64d00d8ab..48f764170f6 100644 --- a/docs/docs/ref-01-top-level-api.md +++ b/docs/docs/ref-01-top-level-api.md @@ -46,7 +46,7 @@ ReactComponent renderComponent( ) ``` -Render a React component into the DOM in the supplied `container`. +Render a React component into the DOM in the supplied `container` and return a reference to the component. If the React component was previously rendered into `container`, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React component. diff --git a/docs/docs/ref-08-reconciliation.md b/docs/docs/ref-08-reconciliation.md index 1b3fcab0f0e..0678d42f65a 100644 --- a/docs/docs/ref-08-reconciliation.md +++ b/docs/docs/ref-08-reconciliation.md @@ -103,7 +103,7 @@ renderB:
secondfirst
=> [replaceAttribute textContent 'second'], [insertNode first] ``` -There are many algorithms that attempt to find the minimum sets of operations to transform a list of elements. [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance) can find the minimum using single element insertion, deletion and substitution in O(n2). Even if we were to use Levenshtein, this doesn't find when a node has moved into another position and algorithms to do that have a much worst complexity. +There are many algorithms that attempt to find the minimum sets of operations to transform a list of elements. [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance) can find the minimum using single element insertion, deletion and substitution in O(n2). Even if we were to use Levenshtein, this doesn't find when a node has moved into another position and algorithms to do that have much worse complexity. ### Keys diff --git a/docs/tips/16-references-to-components.md b/docs/tips/16-references-to-components.md new file mode 100644 index 00000000000..38572276e61 --- /dev/null +++ b/docs/tips/16-references-to-components.md @@ -0,0 +1,31 @@ +--- +id: references-to-components +title: References to Components +layout: tips +permalink: references-to-components.html +prev: expose-component-functions.html +--- + +If you're using React components in a larger non-React application or transitioning your code to React, you may need to keep references to components. `React.renderComponent` returns a reference to the mounted component: + +```js +/** @jsx React.DOM */ + +var myComponent = React.renderComponent(, myContainer); +``` + +If you pass a variable to 'React.renderComponent`, it's not guaranteed that the component passed in will be the one that's mounted. In cases where you construct a component before mounting it, be sure to reassign your variable: + +```js +/** @jsx React.DOM */ + +var myComponent = ; + +// Some code here... + +myComponent = React.renderComponent(myComponent, myContainer); +``` + +> Note: +> +> This should only ever be used at the top level. Inside components, let your `props` and `state` handle communication with child components, and only reference components via `ref`s. diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js index 45b9e688a98..c3e6df3d7c0 100644 --- a/src/core/ReactComponent.js +++ b/src/core/ReactComponent.js @@ -43,11 +43,15 @@ var ComponentLifeCycle = keyMirror({ }); /** - * Warn if there's no key explicitly set on dynamic arrays of children. - * This allows us to keep track of children between updates. + * Warn if there's no key explicitly set on dynamic arrays of children or + * object keys are not valid. This allows us to keep track of children between + * updates. */ -var ownerHasWarned = {}; +var ownerHasExplicitKeyWarning = {}; +var ownerHasPropertyWarning = {}; + +var NUMERIC_PROPERTY_REGEX = /^\d+$/; /** * Warn if the component doesn't have an explicit key assigned to it. @@ -71,10 +75,10 @@ function validateExplicitKey(component) { // Name of the component whose render method tried to pass children. var currentName = ReactCurrentOwner.current.constructor.displayName; - if (ownerHasWarned.hasOwnProperty(currentName)) { + if (ownerHasExplicitKeyWarning.hasOwnProperty(currentName)) { return; } - ownerHasWarned[currentName] = true; + ownerHasExplicitKeyWarning[currentName] = true; var message = 'Each child in an array should have a unique "key" prop. ' + 'Check the render method of ' + currentName + '.'; @@ -95,8 +99,34 @@ function validateExplicitKey(component) { } /** - * Ensure that every component either is passed in a static location or, if - * if it's passed in an array, has an explicit key property defined. + * Warn if the key is being defined as an object property but has an incorrect + * value. + * + * @internal + * @param {string} name Property name of the key. + * @param {ReactComponent} component Component that requires a key. + */ +function validatePropertyKey(name) { + if (NUMERIC_PROPERTY_REGEX.test(name)) { + // Name of the component whose render method tried to pass children. + var currentName = ReactCurrentOwner.current.constructor.displayName; + if (ownerHasPropertyWarning.hasOwnProperty(currentName)) { + return; + } + ownerHasPropertyWarning[currentName] = true; + + console.warn( + 'Child objects should have non-numeric keys so ordering is preserved. ' + + 'Check the render method of ' + currentName + '. ' + + 'See http://fb.me/react-warning-keys for more information.' + ); + } +} + +/** + * Ensure that every component either is passed in a static location, in an + * array with an explicit keys property defined, or in an object literal + * with valid key property. * * @internal * @param {*} component Statically passed child of any type. @@ -113,6 +143,10 @@ function validateChildKeys(component) { } else if (ReactComponent.isValidComponent(component)) { // This component was passed in a valid location. component.__keyValidated__ = true; + } else if (component && typeof component === 'object') { + for (var name in component) { + validatePropertyKey(name, component); + } } } diff --git a/src/core/ReactPropTransferer.js b/src/core/ReactPropTransferer.js index ed721a3c9b5..77d83da6a23 100644 --- a/src/core/ReactPropTransferer.js +++ b/src/core/ReactPropTransferer.js @@ -42,6 +42,8 @@ function createTransferStrategy(mergeStrategy) { /** * Transfer strategies dictate how props are transferred by `transferPropsTo`. + * NOTE: if you add any more exceptions to this list you should be sure to + * update `cloneWithProps()` accordingly. */ var TransferStrategies = { /** diff --git a/src/dom/DOMPropertyOperations.js b/src/dom/DOMPropertyOperations.js index 595fe556d62..b3b83d852cf 100644 --- a/src/dom/DOMPropertyOperations.js +++ b/src/dom/DOMPropertyOperations.js @@ -95,6 +95,10 @@ var DOMPropertyOperations = { return ''; } var attributeName = DOMProperty.getAttributeName[name]; + console.log(attributeName); + if (value && DOMProperty.hasBooleanValue[name]) { + return escapeTextForBrowser(attributeName); + } return processAttributeNameAndPrefix(attributeName) + escapeTextForBrowser(value) + '"'; } else if (DOMProperty.isCustomAttribute(name)) { diff --git a/src/dom/__tests__/DOMPropertyOperations-test.js b/src/dom/__tests__/DOMPropertyOperations-test.js index 1c4b222b5ef..a754afa91c9 100644 --- a/src/dom/__tests__/DOMPropertyOperations-test.js +++ b/src/dom/__tests__/DOMPropertyOperations-test.js @@ -82,12 +82,12 @@ describe('DOMPropertyOperations', function() { expect(DOMPropertyOperations.createMarkupForProperty( 'checked', 'simple' - )).toBe('checked="simple"'); + )).toBe('checked'); expect(DOMPropertyOperations.createMarkupForProperty( 'checked', true - )).toBe('checked="true"'); + )).toBe('checked'); expect(DOMPropertyOperations.createMarkupForProperty( 'checked', diff --git a/src/dom/components/ReactDOMInput.js b/src/dom/components/ReactDOMInput.js index 27148e483ba..b461f711d84 100644 --- a/src/dom/components/ReactDOMInput.js +++ b/src/dom/components/ReactDOMInput.js @@ -130,17 +130,24 @@ var ReactDOMInput = ReactCompositeComponent.createClass({ var name = this.props.name; if (this.props.type === 'radio' && name != null) { var rootNode = this.getDOMNode(); + var queryRoot = rootNode; + + while (queryRoot.parentNode) { + queryRoot = queryRoot.parentNode; + } + // If `rootNode.form` was non-null, then we could try `form.elements`, // but that sometimes behaves strangely in IE8. We could also try using // `form.getElementsByName`, but that will only return direct children // and won't include inputs that use the HTML5 `form=` attribute. Since // the input might not even be in a form, let's just use the global - // `getElementsByName` to ensure we don't miss anything. - var group = document.getElementsByName(name); + // `querySelectorAll` to ensure we don't miss anything. + var group = queryRoot.querySelectorAll( + 'input[name=' + JSON.stringify('' + name) + '][type="radio"]'); + for (var i = 0, groupLen = group.length; i < groupLen; i++) { var otherNode = group[i]; if (otherNode === rootNode || - otherNode.nodeName !== 'INPUT' || otherNode.type !== 'radio' || otherNode.form !== rootNode.form) { continue; } diff --git a/src/utils/__tests__/cloneWithProps-test.js b/src/utils/__tests__/cloneWithProps-test.js index 774b61de1ed..b5aba846aeb 100644 --- a/src/utils/__tests__/cloneWithProps-test.js +++ b/src/utils/__tests__/cloneWithProps-test.js @@ -126,4 +126,56 @@ describe('cloneWithProps', function() { cloneWithProps(, {key: 'xyz'}) ); }); + + it('should transfer children', function() { + var Component = React.createClass({ + render: function() { + expect(this.props.children).toBe('xyz'); + return
; + } + }); + + ReactTestUtils.renderIntoDocument( + cloneWithProps(, {children: 'xyz'}) + ); + }); + + it('should shallow clone children', function() { + var Component = React.createClass({ + render: function() { + expect(this.props.children).toBe('xyz'); + return
; + } + }); + + ReactTestUtils.renderIntoDocument( + cloneWithProps(xyz, {}) + ); + }); + + it('should support keys and refs', function() { + var Component = React.createClass({ + render: function() { + expect(this.props.key).toBe('xyz'); + expect(this.props.ref).toBe('xyz'); + return
; + } + }); + + var Parent = React.createClass({ + render: function() { + var clone = + cloneWithProps(this.props.children, {key: 'xyz', ref: 'xyz'}); + return
{clone}
; + } + }); + + var Grandparent = React.createClass({ + render: function() { + return ; + } + }); + + ReactTestUtils.renderIntoDocument(); + }); }); diff --git a/src/utils/cloneWithProps.js b/src/utils/cloneWithProps.js index 28c3b4540da..310dd08672d 100644 --- a/src/utils/cloneWithProps.js +++ b/src/utils/cloneWithProps.js @@ -21,6 +21,14 @@ var ReactPropTransferer = require('ReactPropTransferer'); +var keyMirror = require('keyMirror'); + +var SpecialPropsToTransfer = keyMirror({ + key: null, + children: null, + ref: null +}); + /** * Sometimes you want to change the props of a child passed to you. Usually * this is to add a CSS class. @@ -42,10 +50,27 @@ function cloneWithProps(child, props) { } var newProps = ReactPropTransferer.mergeProps(child.props, props); - // ReactPropTransferer does not transfer the `key` prop so do it manually. - if (props.key) { + + // ReactPropTransferer does not transfer the `key` prop so do it manually. Do + // not transfer it from the original component. + if (props.hasOwnProperty(SpecialPropsToTransfer.key)) { newProps.key = props.key; } + + // ReactPropTransferer does not transfer the `children` prop. Transfer it + // from `props` if it exists, otherwise use `child.props.children` if it is + // provided. + if (props.hasOwnProperty(SpecialPropsToTransfer.children)) { + newProps.children = props.children; + } else if (child.props.hasOwnProperty(SpecialPropsToTransfer.children)) { + newProps.children = child.props.children; + } + + // ReactPropTransferer does not transfer `ref` so do it manually. + if (props.hasOwnProperty(SpecialPropsToTransfer.ref)) { + newProps.ref = props.ref; + } + return child.constructor.ConvenienceConstructor(newProps); }