We need to move instantiation into the mount phase for context purposes.
To do this I moved the shallow rendering stuff into ReactTestUtils and
reused more of the existing code for it by instantiating a noop child.
Everywhere we refer to the "type" we should pass it to ReactNativeComponent
to resolve any string value into the underlying composite.
...unless they already have a wrapper. Also, add tagName to every wrapper.
This ensures that refs are consistent. They always look like composite
components. This effectively hides the internal implementation details of
real DOM components since you can no longer get a ref to one.
In the future we might want to drop this wrapper and have refs refer
directly to the DOM node.
I currently use a hacky way of auto-wrapping inside of ReactNativeComponent
so that any given string can be wrapped. Better suggestions are welcome.
ReactTextComponent's implementation is DOM-specific; instead of flattenChildren creating the ReactTextComponent instances, ReactNativeComponent now takes care of having ReactTextComponent injected and creating the component instance. I also renamed ReactTextComponent to ReactDOMTextComponent and moved it to browser/ui/ where it belongs. ReactDOMTextComponent no longer inherits directly from ReactComponent and instead implements construct and {mount,receive,unmount}Component directly.
This diff removes `ReactTestUtils.isTextComponent` which should have previously never returned true when using public APIs.
Test Plan: jest, use ballmer-peak example.
Because the JS community's polyfilling infrastructure sucks and we'll
have to fix it for them before we require this.
JSX spread uses React.__spread
(which might get special behavior for key/ref, not sure yet)
This never uses the native implementation and throws for prototype chains.
Once the native implementations are faster, we'll start using them.
This makes it easier to contribute without having to learn a bunch of
slightly different helpers and remember their slightly different
signatures and semantics.
We'll probably start using ES7 spread properties instead of merge in the
future when at least one more transpiler supports it.
This makes ReactDOM a simple helper for creating ReactElements with the string tag as the type. The actual class is internal and created by instantiateReactComponent. Configurable using injection.
There's not a separate class for each tag. There's just a generic ReactDOMComponent which could take any tag name.
Invididual tags can be wrapped. When wrapping happens you can return the same tag again. If the wrapper returns the same string, then we fall back to the generic component. This avoids recursion in a single level wrapper.
If we reconcile components higher in the hierarchy they will likely reconcile components lower in the
hierarchy. If we sort by depth then when we reach those components there will be no more pending state or
props and it will no op.
This fixes a reconciliation bug introduced by adffa9b0f4.
The new unit test case exhibits the bug. When a component that has rendered child components is updated to render inline text, we usually:
# Unmount and remove all child components.
# Set the new inline text content.
However, with batched child operations, we do not **remove all child components** until later. The current implementation will set the inline text content and blow away those nodes, causing a fatal when `ReactMultiChild` later tries to find and remove those nodes.
This fixes the bug by ensuring that text content changes are also enqueued.
Setting `innerHTML` is slow: http://jsperf.com/react-child-creation/2
This reduces the number of times we set `innerHTML` by batching markup generation in a component tree.
As usual, I cleaned up the `ReactMultiChild` module significantly.
== Children Reconciliation ==
When a `ReactNativeComponent` reconciles, it compares currently rendered children, `prevChildren`, with the new children, `nextChildren`. It figures out the shortest series of updates required to render `nextChildren` where each update is one of:
- Create nodes for a new child and insert it at an index.
- Update an existing node and, if necessary, move it to an index.
- Remove an existing node.
This serializable series of updates is sent to `ReactDOMIDOperations` where the actions are actually acted on.
== Problem ==
There are two problems:
# When a `ReactNativeComponent` renders new children, it sets `innerHTML` once for each contiguous set of children.
# Each `ReactNativeComponent` renders its children in isolation, so two components that both render new children will do so separately.
For example, consider the following update:
React.renderComponent(<div><p><span /></p><p><span /></p></div>, ...);
React.renderComponent(<div><p><img /><span /><img /></p><p><img /><span /><img /></p></div>, ...);
This will trigger setting `innerHTML` four times.
== Solution ==
Instead of enqueuing the series of updates per component, this diff changes `ReactMultiChild` to enqueue updates per component tree (which works by counting recursive calls to `updateChildren`). Once all updates in the tree are accounted for, we render all markup using a single `innerHTML` set.
There is a circular dependency between `ReactID`, `ReactMount` and
`ReactInstanceHandles`. Ben and I talked about this today. It seems like the
simplest solution is to consolidate a lot of the code that Ben recently wrote
into `ReactMount`. We can later find ways to trim code out of this module
without causing circular deps.
This fixes known browser bugs with rendering markup using `innerHTML` in IE ([[http://support.microsoft.com/kb/276228 | here is an example of one]]).
This is a subset of what `HTML` (and jQuery) does, and we should eventually consider pulling it out into a separate module to reduce code duplication. For now, this is the minimal set of changes needed to unbreak React in production.
We can afford to use a subset of what `HTML` does because we have the luxury of knowing that the markup is generated sanely with proper closing tags, etc.
When `ReactNativeComponent` updates, it calls `updatePropertyByID` in `ReactDOMIDOperations` which calls `DOMPropertyOperations`. However, in `ReactDOMIDOperations`, we will lookup the node for an ID using `ReactID.getNode`. This wastes time looking for nodes when we may not need to ever update it (e.g. `children`).
This changes `ReactNativeComponent` to bail out sooner.
A dynamic value can be provided as a key to a child. Either as part of an object
or key property. This becomes part of the component's ID.
We have to be careful to escape this key before inserting it into the DOM since
it could become a vulnerability. We fixed this by escaping just the keys.
However, the current implementation breaks when you used escaped keys. The
internal value is escaped and the value used by getAttributeNode and
getElementById are both unescaped.
This fixes that by keeping the unescaped value internally but escaping it right
before the HTML is generated (like any other attribute).
This is important since business logic IDs (that should be used as keys)
contains characters that need to be escaped.
_updateDOMChildren was already updating textContent so we don't need to
do it in _updateDOMProperties. Additionally, don't update .value if
it'll be a noop because it has side-effects (like moving the cursor) in
some browsers (like IE 9).
Refactor tests to be a bit more robust and a bit cleaner too.
Summary: This invariant is unnecessary because `ReactComponent.Mixin.receiveProps` already asserts that this component is mounted. (Being mounted guarantees you have a DOM ID, look at `ReactComponent` and see when `this._rootNodeID` is mutated.)
This fixes an edge case that can cause unnecessary mutations in the DOM. Namely, if a prop is falsey, it will get touched on every update by reconciliation. See unit test.
This cleans up the reconcilation path when adding a `style` prop (going from a falsey or no `style` to having one) by reducing the need for an object allocation and for-loop.
The most obvious manifestation of this bug is visible here:
http://jsfiddle.net/spicyj/zzGas/. In short, when props are removed from a
component, the underlying HTML element doesn't have the attribute
removed.
This change should fix it, but unfortunately it (presumably) makes
_updateDOMProperties a bit slower.
ReactEvent should be reserved for the actual object created when an
event fires. The current ReactEvent is more like EventEmitter than
anything (e.g. it sets up delegation, provides methods to attach and
remove listeners).
We need to make sure that deleteAllListeners() is invoked before we call
the superclass's unmountComponent() method or else we will lose
this._rootNodeID.
I also added an invariant and unit test to make sure we do not break
this in the future.