Compare commits

..

384 Commits

Author SHA1 Message Date
Paul O’Shannessy
a54333842f Bump version for v0.4.0 2013-07-17 11:33:31 -07:00
Paul O’Shannessy
0c1f2720b3 Remove React.autoBind from examples 2013-07-17 11:33:31 -07:00
Paul O’Shannessy
75f7f1e9ba remove likebutton from docs for now
it has some facebook-ism in there and it's probably shouldn't be on the
site.
2013-07-17 11:21:33 -07:00
Paul O’Shannessy
09fbf8e0ca Blog post for 0.4 2013-07-17 11:20:21 -07:00
Paul O’Shannessy
d17d0d5f50 Changelog for 0.4.0 2013-07-17 11:19:58 -07:00
Paul O’Shannessy
1a7a8486ca [docs] Make sure JSX comments code is in block 2013-07-17 11:18:17 -07:00
Paul O’Shannessy
65548db916 Re-order entries in changelog
React is more important than react-tools so put it first!
2013-07-17 10:04:27 -07:00
Paul O’Shannessy
894bb03b23 Add CHANGELOG at the root, remove from downloads page 2013-07-17 10:01:16 -07:00
Paul O’Shannessy
94573545f3 [docs] Prop validation + cleanup default props 2013-07-17 09:47:09 -07:00
Paul O’Shannessy
7734429b89 [docs] Remove @benjamn's TODO for testing 2013-07-17 09:12:26 -07:00
Paul O’Shannessy
35f092afef Write Default Props section 2013-07-17 09:12:02 -07:00
Paul O’Shannessy
b837bb7bdd [docs] fix typo
"L" is not ":"
2013-07-17 08:45:27 -07:00
Paul O’Shannessy
c629a0c5ad Revert "Merge pull request #200 from spicyj/version"
This reverts commit d889322827, reversing
changes made to 156dffb961.
2013-07-17 08:26:59 -07:00
Ben Newman
d889322827 Merge pull request #200 from spicyj/version
Add React.version
2013-07-17 06:13:49 -07:00
Paul O’Shannessy
156dffb961 [docs] Fix broken links 2013-07-17 01:37:52 -07:00
Pete Hunt
e5befc0a73 Update DOM differences docs to include a note about the style attribute. 2013-07-17 01:00:01 -07:00
Paul O’Shannessy
e3f6a6d916 Merge pull request #188 from facebook/docs-refactor
Look ma, new docs!
2013-07-17 00:56:07 -07:00
Paul O’Shannessy
58fecc8cbe Merge branch 'master' into docs-refactor 2013-07-17 00:26:05 -07:00
Paul O’Shannessy
b5aad9479e re-bold some text so it stands out better 2013-07-17 00:25:23 -07:00
Paul O’Shannessy
5db3a0e481 Remove React.autoBind from examples 2013-07-16 23:44:09 -07:00
Paul O’Shannessy
0300f2aa22 em dashes 2013-07-16 23:38:15 -07:00
Paul O’Shannessy
17d36a4cc3 Standardized Markdown lists 2013-07-16 23:35:54 -07:00
Paul O’Shannessy
c222f57b00 Cleanup "Reference"
(unlinked likebutton tutorial - not updated enough for public)
2013-07-16 23:31:32 -07:00
Paul O’Shannessy
1b64508aab Cleanup "Tooling Integration" 2013-07-16 23:31:03 -07:00
Paul O’Shannessy
f2b92d4c7b Cleanup "More About Refs" 2013-07-16 23:30:41 -07:00
Paul O’Shannessy
82f82c7543 Cleanup "Working With the Browser" 2013-07-16 23:30:18 -07:00
Paul O’Shannessy
602623661a Cleanup "Forms" 2013-07-16 23:29:39 -07:00
Paul O’Shannessy
ca3564898d Cleanup "Reusable Components" 2013-07-16 23:29:14 -07:00
Paul O’Shannessy
5b662b43a0 Cleanup "Multiple Components" 2013-07-16 23:28:51 -07:00
Paul O’Shannessy
6ba6fc149a Cleanup "Interactivity and Dynamic UIs" 2013-07-16 23:28:25 -07:00
Paul O’Shannessy
5a3a39aba4 Cleanup "JSX Gotchas" 2013-07-16 23:27:36 -07:00
Paul O’Shannessy
b9b300fcbd Cleanup "JSX in Depth" 2013-07-16 23:24:56 -07:00
Paul O’Shannessy
59f52bce04 Cleanup "Displaying Data" 2013-07-16 23:24:22 -07:00
Paul O’Shannessy
8d3465060d Merge remote-tracking branch 'upstream/docs-refactor' into HEAD 2013-07-16 23:23:32 -07:00
Paul O’Shannessy
d0af08190e Merge pull request #199 from chenglou/patch-1
Tweaked the intro page
2013-07-16 23:01:45 -07:00
Cheng Lou
b343fcaba3 grammar error 2013-07-17 00:15:49 -04:00
Cheng Lou
526099c928 changing back to 'give it five minutes', tweak that sentence 2013-07-17 00:12:21 -04:00
Paul O’Shannessy
f0984cf789 Don't hard code React version anywhere 2013-07-16 21:08:10 -07:00
Ben Alpert
7be14d8155 Add React.version
getConfig needs to be a function because grunt.config.data.pkg.version isn't available at the time that grunt/config/jsx/jsx.js is required.

Test Plan:
grunt build, grunt lint, grunt test all work. After building, both react.js and react.min.js contain the version number.
2013-07-16 20:38:40 -07:00
Cheng Lou
28f6f034ff Tweaked the intro page
Here are some ideas that I think work better:
- 15 repetitions of "React". Removed a few.
- The "two main ideas" weren't clearly separated. I put them under different headers and simplified the wording.
- The "Give it Five Minutes section didn't sound as reassuring. Made it sound more certain.
2013-07-16 21:39:20 -04:00
Paul O’Shannessy
3be6083ea4 Autogen the docs nav
This is still the same amount of duplication, except way easier to
parse. Docs nav now lives in `_config.yml` and you must restart jekyll
to see changes to that list (since config is only read at jekyll launch)
2013-07-16 17:24:41 -07:00
Paul O’Shannessy
f367d0e707 Remove numbers from URLs.
This will let us keep docs in order on the filesystem but have
reasonable permalinks. If we add something at 02- it doesn't result in
lots of broken links.
2013-07-16 17:09:43 -07:00
Paul O’Shannessy
9694a0f7ea Fix title casing and heading levels 2013-07-16 14:52:57 -07:00
Paul O’Shannessy
04bfa545f7 Remove "Scaling Up" 2013-07-16 13:55:42 -07:00
Ben Newman
1971ae8cac Merge pull request #196 from zpao/fix-test-warnings
Fix tests to silence some warnings due to autobinding
2013-07-16 13:15:35 -07:00
Paul O’Shannessy
fd3d16d379 Fix tests to silence some warnings due to autobinding 2013-07-16 13:08:46 -07:00
Paul O’Shannessy
dfd406fe4c Merge pull request #195 from benjamn/force-upgrade-graceful-fs
Upgrade Commoner and Populist to force upgrade to graceful-fs v2.0.0
2013-07-16 12:53:46 -07:00
Ben Newman
301c571405 Upgrade Commoner and Populist to force upgrade to graceful-fs v2.0.0.
A silent upgrade from graceful-fs v1.2.2 to v1.2.3 (a dependency for both
Commoner and Populist) broke the build process, even though tests were
still passing. The 2.0.0 version fixes whatever was broken, though I won't
pretend to know exactly what the root cause was.
2013-07-16 15:42:17 -04:00
Paul O’Shannessy
3376d27915 Merge pull request #194 from benjamn/build-jasmine-bundle-with-populist
Use populist for building jasmine test harness bundle
2013-07-16 11:51:22 -07:00
Ben Newman
0827646695 Use populist for building jasmine test harness package.
We're using populist for building the bundle of test modules and their
dependencies, so it seems worthwhile for consistency to do the same for
the test harness.
2013-07-16 14:48:31 -04:00
Ben Newman
03578e66b5 Fix rootElementsByReactRootID bookkeeping in ReactMount.js.
It's not always possible to update `rootElementsByReactRootID` when the
contents of the container are re-rendered; for instance, when we call
`dangerouslyReplaceNodeWithMarkup` or `dangerouslySetInnerHTML`. Since
this bookkeeping is just trying to warn about potentially problematic
manipulations of the root element, and we can be relatively sure that a
new element with the same ID is logically the same element, this diff
avoids warning in such cases.
2013-07-16 11:43:43 -07:00
Jan Kassens
71e24455a3 add missing argument to invariant in ReactDOMSelect 2013-07-16 11:38:35 -07:00
Tim Yung
83a840656c Fix Markup Rendering in IE
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.
2013-07-16 11:37:26 -07:00
Tim Yung
ed54fff204 Controlled <select> and <option> Components
This implements a `<select>` component that supports `value` and `defaultValue`. It also changes `<option>` to warn when the `selected` prop is supplied.
2013-07-16 11:37:04 -07:00
Jordan Walke
5d4f903482 [React Children] Step Three: New test case for traverseChildren.
If we're going to build utilities off of this, we should have good test
coverage.
2013-07-16 11:36:53 -07:00
Jordan Walke
946029c921 [React Children] Step Two: Use traverseChildren to perform mapChildren
Use the new `traverseChildren` utility to perform `mapChildren`.
The goal is to get as close to the bavior of the semantics of
`Array.prototype.map`, but also in a way that understands deeply nested arrays
and objects.
2013-07-16 11:36:11 -07:00
Jordan Walke
f4321f8624 [React Children] Step One: Refactor flattenChildren
Refactoring `flattenChildren` so that the traversal code is reusable
for other purposes.
2013-07-16 11:35:43 -07:00
Paul O’Shannessy
46d05b1191 Sync vendor modules from FB.
Biggest win here is that we'll strip out the console.error from
EventListener and we won't need to suggest people use a console
polyfill with the minified build.
2013-07-15 21:04:43 -07:00
Ben Newman
558e8ca312 Merge pull request #193 from benjamn/use-populist-for-testing
Fix tests
2013-07-15 16:42:57 -07:00
Ben Newman
b763d7d029 Use a separate grunt/config/jsx config file for tests.
No longer injecting __MOCK__ as a global constant (it's just a config
property now).

Turns out the `grunt jsx:debug` task was never necessary for tests.
2013-07-15 19:41:40 -04:00
Ben Newman
2d61639f90 Store dirtyMocks array globally so it can survive dumpCache().
When require("mock-modules").dumpCache() is called, all mock functions
previously created continue to refer to the old dirtyMocks array.

If we replace that array with a new one, those mock functions will never
have their .mockClear() methods called again.

The upstream version of mocks.js pulls a similar global trick, and I never
understood why until now.
2013-07-15 19:41:40 -04:00
Ben Newman
204796868d Enable module.exports mocking in react-test.js.
We don't currently attempt to mock modules automatically, but we do
respect require("mock-modules").mock, .dontMock, and .dumpCache.

I'm going to keep investigating auto-mocking, since that would move us
much closer to the behavior used within Facebook.

Closes #154.
Closes #155.
2013-07-15 19:41:40 -04:00
Ben Newman
37014e1002 Call require("mock-modules").register in every mockable module.
Mocking happens only when config.constants.__MOCK__ is true.
2013-07-15 18:10:49 -04:00
Ben Newman
c6c4657f83 Use populist v0.1.2 to bundle test modules instead of browserify.
This will allow full support for mocking, dumpCache, and correct line
numbers in error messages.
2013-07-15 18:10:49 -04:00
Ben Newman
f457394362 Remove remaining calls to ReactCompositeComponent.autoBind.
This is causing console.warning spew in the open source tests.
2013-07-15 15:09:01 -07:00
Tim Yung
2b97c608f8 Normalize DefaultDOMPropertyConfig
For consistency, use lowerCamelCase for all props in `DefaultDOMPropertyConfig`.
2013-07-15 15:08:46 -07:00
Paul O’Shannessy
15493530f1 Revert "Update LICENSE"
This reverts commit dd1d49b360.

The license is actually supposed to look like that. That section is
boilerplate for others to apply the license to their own work. See
immediately above...

> To apply the Apache License to your work, attach the following
> boilerplate notice, with the fields enclosed by brackets "[]"
> replaced with your own identifying information. (Don't include
> the brackets!)
2013-07-15 10:40:27 -07:00
Pete Hunt
dd1d49b360 Update LICENSE 2013-07-15 00:28:36 -07:00
petehunt
e9e8934577 fix markup bugs 2013-07-14 18:46:50 -07:00
petehunt
2397e35cdd switch to guides 2013-07-14 18:43:33 -07:00
petehunt
0e585d8102 put it all together 2013-07-14 18:42:39 -07:00
petehunt
6009934176 cleanup some files, move tutorial 2013-07-14 18:35:59 -07:00
petehunt
b20c2641d4 move docs around, add likebutton 2013-07-14 18:31:46 -07:00
petehunt
445a0dac37 tweak the source 2013-07-14 18:25:51 -07:00
petehunt
33abe80b59 scaling up fix 2013-07-14 18:00:17 -07:00
petehunt
d7cf1c509b delete old docs 2013-07-14 17:57:39 -07:00
petehunt
7d97f26870 add more examples 2013-07-14 17:52:25 -07:00
petehunt
b2107ba80b update and move tutorial 2013-07-14 17:49:07 -07:00
petehunt
d0c431a2a3 Fix comma 2013-07-14 17:15:28 -07:00
Pete Hunt
ab7ef4ed3b Update 08-working-with-your-environment.md 2013-07-14 17:07:58 -07:00
petehunt
204edb4a27 fix some links 2013-07-14 17:07:57 -07:00
Pete Hunt
3c742d50b6 Update 03-interactivity-and-dynamic-uis.md 2013-07-14 17:07:57 -07:00
Pete Hunt
1c51cc34cd Update 02.1-jsx-in-depth.md 2013-07-14 17:07:57 -07:00
Pete Hunt
64b9b55a0d Update 02.2-jsx-gotchas.md 2013-07-14 17:07:57 -07:00
Pete Hunt
f9741b0728 Update 01-motivation.md 2013-07-14 17:07:57 -07:00
petehunt
3085254a91 reorg 2013-07-14 17:07:57 -07:00
petehunt
0de35588c1 jsx gotchas, reference 2013-07-14 17:07:57 -07:00
petehunt
21ea1ac61e add working with your environment section 2013-07-14 17:07:57 -07:00
petehunt
4fccaa514b @vjeux comments 2013-07-14 17:07:57 -07:00
petehunt
13ad0c500b antipatterns 2013-07-14 17:07:57 -07:00
petehunt
126a7f5c11 more jsx handholding 2013-07-14 17:07:57 -07:00
petehunt
bb3bd76fe9 Reorg docs, write a lot of content, import a lot of stuff from dex 2013-07-14 17:07:57 -07:00
petehunt
4bbdcdb0b8 another comment 2013-07-14 17:07:57 -07:00
petehunt
d294a7f30f more scaling up 2013-07-14 17:07:57 -07:00
petehunt
de8d0e35a2 more state machiens 2013-07-14 17:07:57 -07:00
petehunt
faa84b5b85 further improvements 2013-07-14 17:07:57 -07:00
petehunt
e1e5f17b27 link to blog 2013-07-14 17:07:57 -07:00
petehunt
0e63000b5c even better toc 2013-07-14 17:07:57 -07:00
petehunt
151997b1e1 Start on section 04 2013-07-14 17:07:57 -07:00
petehunt
a36bcd33c6 more toc 2013-07-14 17:07:57 -07:00
petehunt
9194fea915 Add table of contents info 2013-07-14 17:07:57 -07:00
petehunt
67d9891926 add seconds 02.1 and 03 2013-07-14 17:07:56 -07:00
petehunt
05341fb3b3 Add more jsx docs rather than linking 2013-07-14 17:07:56 -07:00
petehunt
c82afd7e54 Some style changes 2013-07-14 17:07:56 -07:00
petehunt
ed98f2ca57 Make less facebooky 2013-07-14 17:07:56 -07:00
petehunt
462e450bb3 First two sections of newdocs 2013-07-14 17:07:56 -07:00
Paul O'Shannessy
06e5fcc010 "use strict" for ReactDoNotBindDeprecated 2013-07-12 15:42:46 -07:00
Paul O'Shannessy
ac84652e50 Cleanup console.* uses
* Stop doing `global.console && ...`
* Make sure all uses are behind `__DEV__` checks so they get stripped out
2013-07-12 15:42:25 -07:00
Tim Yung
eee3980749 Stringify value in ReactDOMInput / ChangeEventPlugin
This fixes two bugs related to string-casting in React:

 # Setting `<input value={0} />` would use an empty `value` because `0` is falsey.
 # Using `onChange` and `setState` with non-strings could lead to an infinite loop.

The latter is possible with controlled inputs when:

 - User changes input value.
 - `onpropertychange` fires.
 - `ChangeEventPlugin` dispatches `onChange`.
 - A handler responds via `this.setState` with a non-string value (e.g. a number).
 - The input re-renders and re-sets `value`.
 - The new `value` is not a string, but the current `value` (read from the element) is cast to a string automatically by the browser.
 - This triggers another `onpropertychange`.
 - `ChangeEventPlugin` dispatches another `onChange`.
 - ...
2013-07-12 15:40:55 -07:00
Paul O'Shannessy
cf83fbe397 Remove references to React.autoBind
Cleaned up a comment and removed a useless test.
2013-07-11 23:07:01 -07:00
Paul O’Shannessy
e221ff7cd4 Add exports to globals for jshint
This is a bit unfortunate, but it'll shut lint up for the time being. We
can't just change the modules to use `module.exports = { ... }` due to
how we handle circular dependencies internally (`ReactMount` require
`ReactID` and vice versa).
2013-07-11 17:20:21 -07:00
Paul O’Shannessy
607de16d82 New blog post: Props in v0.4 2013-07-11 15:52:29 -07:00
Paul O'Shannessy
d762627312 Rename props to propTypes
This does two things:

 - Rename `props` to `propTypes`.
 - Rename `ReactProps` to `ReactPropTypes` (and `React.Props` to `React.PropTypes`)
2013-07-10 15:06:18 -07:00
Tim Yung
10dab495f2 Stop Unnecessary Purging of Node Cache
When each component unmounts, it already cleans up its respective entry in the node cache. Let's stop blowing away the entire node cache unnecessarily.

This should improve performance because a React component's root will never need to be searched for more than once.
2013-07-10 15:05:18 -07:00
Tim Yung
ee1335b6a2 Delete setTextNodeValueAtIndexByParentID
This does not appear to be used anywhere.
2013-07-10 15:05:06 -07:00
Tim Yung
8687645c50 Reduce Lookup for Missing Lifecycle Methods
This is a micro-optimization that reduces the lookup time for missing lifecycle methods. The extra amount of memory is linear to the number of components that exist on a page which I think is a worthwile trade-off.
2013-07-10 15:04:44 -07:00
Tim Yung
7b68fcd408 Short-circuit updatePropertyByID
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.
2013-07-10 15:04:23 -07:00
Pete Hunt
92dab0759c More autobind warnings and invariants
This adds two new warnings and one new invariant:

- Warn when using React.autoBind() that it is deprecated.
- Throw when calling bind() on an autobound method with the wrong value of "this". Today we'll silently ignore the provided value, which is confusing.
- Warn when calling bind() on an autobound method with the *right* value of "this" and no other arguments. This is already done for you by React.
2013-07-10 15:03:50 -07:00
Ben Newman
5c6e59f53c Don't ignore children of non-ancestor ID nodes in findComponentRoot. 2013-07-10 14:58:57 -07:00
Tim Yung
067fe27699 Fix Clowny Validation Code
ಠ_ಠ
2013-07-10 14:58:47 -07:00
Paul O’Shannessy
8db2ba9130 Upgrade other dependencies
Nothing groundbreaking. `semver` was likely already installed at 2.0.x
anyway (since >=) and `grunt-contrib-jshint` just gives us `jshint`
@ 2.1.3
2013-07-09 10:55:56 -07:00
Paul O’Shannessy
1500e9810c Upgrade browserify.
There are other changes I'm sure but the most important is that module
sorting results in deterministic builds.

The biggest win here comes for releases. Previously we had to jump
through hoops to make sure the files we put in bower were the same files
we put on the CDN, were the same files packaged in the Ruby gem, were
the same files we packaged into a zip file, were the same file we used
when create PRs to CDNJS. Rebuilding docs also resulted in conflicting
versions so we had to be careful when committing. This takes away all of
that pain. We can build from the same revision and get the same files.
2013-07-09 09:20:57 -07:00
Paul O’Shannessy
a3b21b10e4 Merge pull request #178 from paulshen/master
Add __benchmarks__ to .gitignore
2013-07-08 18:27:39 -07:00
Paul Shen
fa03e98426 Add __benchmarks__ to .gitignore 2013-07-08 16:40:21 -07:00
Paul O'Shannessy
203dba271b Add some missing attributes
Mostly this was to better support some HTML5 stuff. This was not a complete pass through though, and we should probably add more.
2013-07-08 13:58:42 -07:00
Paul O’Shannessy
91562ba934 Sync JSX tags from upstream
This is the other part of fc5f7e0e85. This
file isn't part of our sync process so needs to be done separately.
2013-07-08 12:51:11 -07:00
Paul O'Shannessy
fc5f7e0e85 Add support for missing html elements
We're missing a bunch of elements. So I scraped them from https://developer.mozilla.org/en-US/docs/Web/HTML/Element. Here's the script I used (run from Firefox scratchpad):

```
Array.prototype.slice.call(document.querySelectorAll('div.index.widgeted li'))
  .filter(function(li) {
    return !/deprecatedElement|obsoleteElement|nonStdElement/.test(li.firstChild.className)
  })
  .map(function(li) {
    // <tag> -> tag
    return li.querySelector('code').textContent.replace(/<(.+)>/,'$1');
  })
  .join(': false,\n  ');
```

I had to filter a couple more out (because there's some malformed content), but then it was simply merge with what we had and check to see if the new ones needed to omit the close tag.
2013-07-08 11:51:47 -07:00
Ben Newman
a4123a069e Continue over ID-less children in ReactMount.findComponentRoot.
This fixes our perf test by coping with edge cases like the
injection of `<tbody>` between `<table>` and `<tr>` nodes, which occurs
automatically in some browsers when we set `.innerHTML`.

Introducing more search branches would be risky if not for my previous
commit that made `findComponentRoot` breadth-first instead of depth-first.
2013-07-08 11:50:55 -07:00
Ben Newman
a5ddb07cb3 Make ReactMount.findComponentRoot breadth-first & non-recursive.
This function needs to be as fast as possible for those cases when
`ReactID.getNode` can't rely on the `nodeCache`.

Breadth-first search prevents us from diving too deeply down the wrong
branches when the sought-after node can be found at a shallower level.

The queue required for breadth-first search is implemented by a single
array indexed by `childIndex`. To save space, only the `.firstChild` nodes
are stored, and we use `.nextSibling` to iterate over the other siblings
in a `while` loop.
2013-07-08 11:50:34 -07:00
Ben Newman
917e101c2c Try harder to find container in ReactMount.findReactContainerForID.
When we render a new component into a container, we now record a reference to the rendered DOM node as `rootElementsByReactRootID[reactRootID]`, so that we can determine the actual container later on, in case `containersByReactRootID[reactRootID]` is no longer the true container.
2013-07-08 11:50:13 -07:00
Pete Hunt
5c624021ea Tweaks to make the Closure parser happy
This doesn't make it actually run with Closure, but passes its parser.
2013-07-08 11:50:02 -07:00
Pete Hunt
5676a486cf Allow nested ReactUpdates 2013-07-08 11:49:42 -07:00
Pete Hunt
1658feade8 Remove the check for console.warn
We already polyfill this at FB and we should recommend https://github.com/paulmillr/console-polyfill for open source.
2013-07-08 11:49:28 -07:00
Pete Hunt
5f1eceb1ee Merge pull request #176 from andreypopp/master
Add datetime to the list of known DOM attributes
2013-07-08 00:44:33 -07:00
Andrey Popp
fc3491e0d0 dateTime attr: camelCase and MUST_USE_ATTRIBUTE 2013-07-08 11:23:43 +04:00
Andrey Popp
9f94244994 add datetime to the list of known attributes
as per http://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#attr-time-datetime
2013-07-08 00:09:38 +04:00
Pete Hunt
6ebdd0cfd2 Merge pull request #173 from chenglou/patch-4
Emphasis on single child.
2013-07-06 23:20:20 -07:00
Cheng Lou
858377946f Emphasis on single child. 2013-07-06 16:10:34 -04:00
Ben Newman
826d603b05 Merge pull request #167 from benjamn/issue-166-fix-phantomjs-executability
Ensure that the phantomjs binary has appropriate UNIX mode
2013-07-05 16:18:05 -07:00
Ben Newman
da4b761c45 Don't require tmp module unless we're going to use it. 2013-07-05 19:17:20 -04:00
Ben Newman
70a2f8046c Ensure that the phantomjs binary has appropriate UNIX mode.
The 755 mode corresponds to a UNIX mode string of -rwxr-xr-x.

Closes #166.
2013-07-05 19:06:49 -04:00
Christopher Chedeau
51bf95f6d1 Merge pull request #157 from vjeux/communit_4
Community Round-up #4
2013-07-03 18:32:36 -07:00
Vjeux
2246f530af Community Round-up #4 2013-07-04 03:25:35 +02:00
Paul O’Shannessy
4e04ef0769 Merge pull request #153 from lrowe/patch-2
Add `hidden` attribute to DOM properties.
2013-07-03 15:48:19 -07:00
Laurence Rowe
b2bbdf8cbf Add hidden attribute to DOM properties. 2013-07-03 14:05:44 -07:00
petehunt
9178208ba8 update docs 2013-07-03 13:09:10 -07:00
Pete Hunt
7d3db0e5ed Add finally block for clearing ReactUpdates state
If any component throws during reconciliation any subsequent reconciliation will break badly because ReactUpdates will be in an inconsistent state.
Add a finally block to prevent this.
2013-07-03 11:38:50 -07:00
Tim Yung
2869e5b4df Inject ReactDOMInput
Changes `<input>` in React to use `ReactDOMInput` which supports `value` and `defaultValue` instead of the current, arguably broken `value` behavior.
2013-07-03 11:38:45 -07:00
Tim Yung
510146eb6d Faster Listener Deletion
Whenever a component is unmounted, we delete all listeners that might have been attached. This sucks because most applications, Facebook included, do not use every listener. There's a lot of wasted computation, especially if many components are mounted and unmounted.

This changes `deleteAllListeners` to more delete listeners more efficiently.
2013-07-03 11:38:34 -07:00
Tim Yung
c692d9e844 Fix ChangeEventPlugin for IE8 and IE9
This fixes a bug with `ChangeEventPlugin` in IE8 and IE9. The extend of this bug includes:

 - On IE8, not firing `onChange` immediately after `value` of an input is changed.
 - On IE9, not firing `onChange` when backspacing.
2013-07-03 11:38:31 -07:00
Pete Hunt
32423a83fc Injectable DOMProperty configs, and add back ID attribute
https://github.com/facebook/react/pull/141
2013-07-02 18:30:04 -07:00
Sebastian Markbage
d50148591b Introduce a supported way to slice children
Introduces a counterpart to mapChildren. It excludes empty children just as
mapChildren for compatibility. With might introduce something like
sliceChildrenIncludingEmptyValues at some point.
2013-07-02 18:30:04 -07:00
Pete Hunt
b6451be582 Update syntax.md
Some inconsistencies pointed out in FB comments
2013-07-02 18:27:15 -07:00
Paul O’Shannessy
5e296d7af8 Merge pull request #150 from benjamn/commoner-dir-watcher
Upgrade Commoner to --watch directories instead of individual files
2013-07-02 17:21:39 -07:00
Pete Hunt
3093a476b1 Merge pull request #115 from spicyj/async-state
Batch together calls to setState, setProps, etc
2013-07-02 17:16:07 -07:00
Paul O’Shannessy
00e56c5155 Merge pull request #151 from spicyj/bq
Add blockquote tag (already in JSX)
2013-07-02 17:14:34 -07:00
Pete Hunt
8b9891aa8a Remove autobinding warning
I think this warns on many legitimate use cases. We should get rid of it I think.
2013-07-02 16:53:39 -07:00
Ben Alpert
f7901a2380 Add blockquote tag (already in JSX) 2013-07-02 16:19:41 -07:00
Ben Newman
5c4352b57b Upgrade Commoner to --watch directories instead of individual files.
This behavior is new in Commoner v0.8.3, following the incorporation of
@jeffreylin's `DirWatcher` implementation:
https://github.com/jeffreylin/jsx_transformer_fun/blob/master/dirWatcher.js

Watching directories instead of files reduces the total number of open
files, and copes better with editors that save files by deleting and then
immediately recreating them.

Closes #60.
Closes #71.
2013-07-02 17:47:52 -04:00
Paul O’Shannessy
9ca7c9631a Blog: Autobind by Default
Kicking off the series of posts previewing v0.4
2013-07-02 13:04:14 -07:00
Ben Alpert
d9e99d4688 Batch together calls to setState, setProps, etc
The end of ReactUpdates-test.js is probably most illuminating for seeing how this works.
2013-07-02 00:04:50 -07:00
Pete Hunt
3fd56b4038 Merge pull request #147 from lrowe/patch-1
Add figure and figcaption elements to React.DOM (already supported by jsx)
2013-07-01 17:51:42 -07:00
Laurence Rowe
44659df598 Add figure and figcaption elements to React.DOM (already supported by jsx.) 2013-07-01 17:03:54 -07:00
Ben Alpert
0e9ee239a9 Merge branch 'master' into async-state 2013-07-01 16:08:57 -07:00
Paul O’Shannessy
ce0704a491 Merge pull request #145 from benjamn/no-jsx-dependency-scanning-by-default
Bump Commoner version to disable dependency scanning by default
2013-07-01 13:57:12 -07:00
Paul O’Shannessy
0acc1d8c78 Merge pull request #144 from benjamn/fix-EMFILE-jsx-errors
Bump Commoner version to fix EMFILE errors
2013-07-01 13:54:09 -07:00
Ben Newman
18ef8962f1 Bump Commoner version to disable dependency scanning by default.
If you are using bin/jsx independently, you may need to pass
--follow-requires to it if you rely on its dependency scanning.

Dependency scanning is still a good idea, but it's difficult to make it
work perfectly for everyone the first time they try bin/jsx.

Closes #131.
2013-07-01 16:50:35 -04:00
Ben Newman
e748be32da Bump Commoner version to fix EMFILE errors.
Finally found a more robust solution for the "too many open files"
problem: https://github.com/benjamn/commoner/commit/ad72ba42db.

Closes #137.
Closes #138.
2013-07-01 16:31:56 -04:00
Paul O’Shannessy
0ad14fc038 Merge pull request #143 from spicyj/ie8-fixes-2
Use proper names for scroll metric properties
2013-07-01 13:12:14 -07:00
CommitSyncScript
dd61439061 Revert Flattening of Children
I still think the semantics of flattening children is valid but we'll
want to revert the flattening implementation while we solidify the
semantics and try another approach.

This reverts flattening so that this.props.children can once again be
either a single child, flat array or nested array.

mapChildren calls flattenChildren before doing anything else. This is
not the most efficient approach but I wanted to keep this inital diff
simple. It also ignores empty values for backwards compatibility.

We may want to try another approach where empty values are included
in the map.

Validation of keys is still done inside ReactComponent. Ideally I'd
like to extract that into a separate module but to avoid cyclic
dependencies, I'm keeping it in ReactComponent for now.
2013-07-01 13:01:52 -07:00
CommitSyncScript
40bebf0c86 Fix ReactDOMInput and ReactDOMTextarea Race Condition
This fixes a race condition if the `onClick` tries to update the input or textarea (e.g. by calling `setState`):

  <input
    onClick={function(event) {
      this.setState({somethingElse: true}); // Triggers an update.
      // event.target.value is now equal to the old value, fail...
      this.props.onChange(event);
    })
  />
2013-07-01 13:00:41 -07:00
Ben Alpert
2aa5631e2e Use proper names for scroll metric properties 2013-07-01 11:54:13 -07:00
Paul O’Shannessy
f39a0f8e40 Merge branch 'no-content' of git://github.com/spicyj/react into spicyj-no-content
Conflicts:
	src/core/ReactNativeComponent.js
2013-06-28 16:42:59 -07:00
CommitSyncScript
ca19ffb083 use .apply instead of .call in ReactCompositeComponent
This was a bit ridiculous.. let's just use arguments as it's supposed to
be used.
2013-06-28 16:36:54 -07:00
CommitSyncScript
15272f30f4 Don't keep the HTML escaped ID internally, only in HTML generation
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.
2013-06-28 16:35:45 -07:00
CommitSyncScript
55176116a2 Implement ReactDOMTextarea
This changes `ReactDOMTextarea` to accept `defaultValue` and `value`. It will warn people about using children (but allow it and treat it as `defaultValue`, which is the current behavior).
2013-06-28 16:35:04 -07:00
CommitSyncScript
738de8cfa8 Improve findComponentRoot Error Message
Instead of simply logging the React ID of the `ancestorNode` when `findComponentRoot` fails, use a `try ... finally` to `console.error` the `ancestorNode`. This allows modern web inspectors to actually log a reference to the node (which may not have a React ID).

This means when people run into the problem, they will not have to execute:

  require('ReactID').getNode(<copy+paste>);

NOTE: Admittedly, this will not log anything in IE8. That's fine, since IE8 has shitty console logging anyway.
2013-06-28 16:34:27 -07:00
Paul O’Shannessy
bd150ec658 Clean up unused variables for lint 2013-06-28 16:34:19 -07:00
Paul O’Shannessy
ee21a604f3 Delete files that accidentally got merged in
ಠ_ಠ
2013-06-28 14:54:07 -07:00
yungsters
43358157cf Merge branch 'textarea-update-value' of git://github.com/spicyj/react
Conflicts:
	src/core/ReactDefaultInjection.js
2013-06-28 14:30:47 -07:00
Paul O’Shannessy
0b65d7555e Merge pull request #136 from jeffmo/empty_expressions
Add support for empty XJS expressions
2013-06-28 14:16:23 -07:00
CommitSyncScript
8bc2abd367 .reactRoot[base10] -> .r[base36]
Just a bit of byte savings for server rendering. Props to @benjamn for the base36 idea (and for making this diff easy).

With a little work we could probably get rid of the .r as well.
2013-06-28 13:48:08 -07:00
CommitSyncScript
6556881417 Throw on Missing Elements
This changes React to throw when `ReactID.getNode()` fails to find a node. This method is used by two call sites:

 - Implements `ReactComponent#getDOMNode`. This method already throws if a component is not mounted, and //all mounted components should be able to find their rendered root nodes//.
 - Used by `ReactDOMIDOperations`. These call sites aleady assume that `getNode` returns a non-null. Currently, if the node is not found, this is the site that fatals (and the stack trace is much harder to debug).

The error message should make it //a lot// easier to debug unexpected DOM trees. In particular, this will help track down all the places where the browser inserts `<tbody>` unexpectedly.
2013-06-28 13:47:31 -07:00
CommitSyncScript
c54900f63e Fix Composition Level of Components w/o Owners
This fixes a bug with components constructed with no owners, for example:

  // Both the <div> and <span> have no owners.
  React.renderComponent(<div><span /></div>, node);

They should have a composition level of 1 and their keys should be prefixed with 0 to indicate they were created without owners. However, they currently incorrectly get a composition level of 0 (which means that //their// children will have keys prefixed with 0, which is wrong).
2013-06-28 13:47:05 -07:00
JeffMo
9c35189ad1 Add support for empty XJS expressions 2013-06-28 13:10:20 -07:00
Ben Alpert
e998041229 Remove content property
Fixes #119.
2013-06-28 11:14:59 -07:00
Christopher Chedeau
431e1d5608 Merge pull request #135 from vjeux/bugbuster
Add link roundup
2013-06-28 09:36:45 -07:00
Vjeux
d4c7991aee Add link roundup 2013-06-28 18:35:52 +02:00
Christopher Chedeau
811df48756 Merge pull request #123 from vjeux/patch-2
Add @jsx common parser issues in the docs
2013-06-27 17:17:59 -07:00
CommitSyncScript
dbd9d99bcd Fix findComponentRoot w/ Unidentified Nodes
The current `ReactInstanceHandles` has a bug where `findComponentRoot` barfs if it comes across a node that was not identified by React (via `ReactID`). This fixes that.

This was always a bug, but it became more apparent once we switched to `data-reactid` because arbitrary `document.createElement`'d nodes are much more likely to have an `id` than they are to have a `data-reactid`.
2013-06-27 16:44:08 -07:00
Paul O’Shannessy
c032743b93 Bump version to 0.4.0a
This should have happened a while ago, but better late than never.
2013-06-27 13:30:09 -07:00
Christopher Chedeau
418c1fc427 Add @jsx common parser issues in the docs
Update getting-started.md
2013-06-27 22:27:54 +02:00
petehunt
fe30279ed0 Merge pull request #126 from vjeux/community_3
Community Roundup #3
2013-06-27 13:19:48 -07:00
Vjeux
b4f096364f Community Roundup #3 2013-06-27 22:18:26 +02:00
Paul O’Shannessy
9e6a581f68 Merge pull request #132 from petehunt/engines
Recommend Node v0.10.0 due to https://github.com/isaacs/npm/issues/2907
2013-06-27 13:14:28 -07:00
petehunt
f7e49f3b25 Recommend Node v0.10.0 due to https://github.com/isaacs/npm/issues/2907 2013-06-27 13:09:29 -07:00
Paul O’Shannessy
2fd5efd92b Merge pull request #128 from mathieumg/master
Double quotes for strings in JSON data
2013-06-26 15:52:44 -07:00
CommitSyncScript
67cf44e7c1 Change ReactID.ATTR_NAME to "data-reactid"
This final change is what we've all been waiting for.

Note that it no longer makes sense to use `document.getElementById` in
`getNode`, because that only ever worked with "id" attributes.
2013-06-26 14:31:50 -07:00
CommitSyncScript
9ceaff7318 Refactor of ChangeEventPlugin
some refactoring and also handle if `blur` doesn't fire on the form
input in IE8 (by always cleaning up on focus). per discussions with
@balpert
2013-06-26 14:31:33 -07:00
Mathieu M-Gosselin
703c825196 Changed JSON data in the tutorial to use double quotes for strings. 2013-06-26 16:24:51 -04:00
Ben Newman
86adcd6766 Merge pull request #124 from zpao/keep-compare-size-cache
The compare_size cache gets wiped out with `grunt clean`
2013-06-26 11:57:03 -07:00
CommitSyncScript
5a85c5e535 Don't pass a null context to Function.prototype.call.
This prevents PhantomJS tests from hanging in the open-source React repo.

Until the advent of `"use strict"`, passing `null` as the context object
to `.call` or `.apply` resulted in `this` taking on the value of the
global object inside the invoked function.

Technically the `"use strict"` directive is supposed to make it possible
that `this === null`, but strict mode is not respected by all browsers,
including (unfortunately) PhantomJS.

Since these `expect`-ations are just testing binding behavior, let's not
make them also test strict mode `this` handling.
2013-06-26 11:23:25 -07:00
CommitSyncScript
cf926338bf Fix onChange for File Input
Makes sure that `onChange` fires for file inputs.
2013-06-26 11:23:13 -07:00
CommitSyncScript
43930455de Cache Default Props
The `getDefaultProps` return value should not be dependent on any external data (including `this.props` and `this.state`), so the return value should be consistent everytime we call it.

This caches the return value so we do not do work and allocate memory unnecessarily.
2013-06-26 11:22:39 -07:00
Paul O’Shannessy
6f04bd9410 Merge pull request #112 from spicyj/check-dom-nodetype
Check that `container` is a valid DOM element
2013-06-25 14:22:21 -07:00
CommitSyncScript
59212a538e Remove deleted files again
These accidentally got re-added in a rebase
2013-06-25 14:15:35 -07:00
CommitSyncScript
a9b024330c Make @typechecks static-only 2013-06-25 14:01:15 -07:00
CommitSyncScript
d93761af62 Enforce nodeCache validity
A node is considered valid if it

  1. has the expected ID (more of a sanity check than something that is
     ever likely to go wrong), and

  2. is contained by a currently mounted container.

When these requirements are met, we can be confident that
`ReactMount.findReactRenderedDOMNodeSlow(node.id) === node`, which is
important for cache consistency because `findReactRenderedDOMNodeSlow` is
what we fall back to when we don't find a node in the cache.

Point 2 is a subtle requirement, because it allows nodes to be valid even
if they are not currently contained by a document. Rendering into a
detached node is okay, in other words (which is something that
`document.getElementById` never properly accounted for).

Containment testing takes linear time in the depth of the DOM, which
sounds unfortunate until you realize that virtually all browsers support a
native `ancestor.contains(descendant)` method, and in practice the vast
majority of nodes are either orphaned with `.parentNode === null` or not
very deep relative to their container.
2013-06-25 14:00:31 -07:00
CommitSyncScript
fb6381fb35 Upgrade TextChangeEventPlugin to ChangeEventPlugin and support more form elements
Upgrade `TextChangeEventPlugin` to be the `onChange` event that React
fires. In React, `onChange` will now fire when `input` fires for form elements in
modern browsers.

Handle this for:

  input[type=text]
  input[type=password]
  input[type=checkbox]
  input[type=radio]
  textarea
  select

Support:

- OSX Chrome
- OSX Safari
- OSX Firefox
- Win 7 / IE8
- Win 7 / IE9
- Win 7 / IE10

Everything works but caret selection / placement differs from browser to
browser.

For <select> elements, the event is fired with `change`. This is a
conscious decision, even though in some browsers (OSX firefox, IE), it
can be argued that the event should fire more due to how the UI looks.

Builds on https://github.com/facebook/react/pull/75, which handled only
text inputs.
2013-06-25 13:58:59 -07:00
CommitSyncScript
1d65f81b16 Remove ReactDOMNodeCache and getDOMNodeID.
These modules have been superseded by `ReactID`. Since they were only used
internally, and I have updated all client code that previously assumed
their existence, I believe they can be dropped for good.
2013-06-25 13:58:43 -07:00
CommitSyncScript
1c40dde782 Remove ReactID.primeTree in favor of priming in ReactID.getID.
Although it would have been nice to prime the entire tree and achieve a
cache hit rate of 100%, that cost would have to be paid up front, during
page rendering.

This patch avoids priming up front in favor of making the most of the work
done by `ReactMount.findReactRenderedDOMNodeSlow`, which calls
`ReactID.getID` while traversing the rendered DOM. The insight is this: if
`getID` simply primes the cache whenever it finds a new ID, then
`findReactRenderedDOMNodeSlow` will end up priming quite a few more nodes
that are actually involved in `ReactID.getNode` lookups, and we won't need
`primeTree` at all.
2013-06-25 13:57:54 -07:00
Ben Alpert
f6c4d2d161 Check that container is a valid DOM element 2013-06-25 10:54:28 -07:00
Paul O’Shannessy
b282a0f4f1 The compare_size cache gets wiped out with grunt clean
`.grunt/` is the directory that is suggested for storing task related
files, and this file is not useful if it gets wiped out often. So I'm
moving the compare_size cache into `.grunt/` so we keep it around
longer.
2013-06-24 21:35:00 -07:00
CommitSyncScript
c1886c6513 Use ReactID.ATTR_NAME as the React-specific ID attribute name.
Another step in the plan towards making `ReactID.ATTR_NAME` the central
source of truth regarding the React-specific ID attribute name.
2013-06-24 18:28:29 -07:00
CommitSyncScript
bd8ecc1caa Restore @spicyj's lifecycle tests
These got lost in a rebase.
2013-06-24 18:28:18 -07:00
CommitSyncScript
6bbcbc08cf Rename BrowserEnv to ViewportMetrics
Becuase that's what it is.
2013-06-24 18:28:16 -07:00
CommitSyncScript
f0a4ca5f69 Remove React.autoBind entirely 2013-06-24 18:28:12 -07:00
CommitSyncScript
de40842597 Use @return, not @returns 2013-06-24 16:16:46 -07:00
CommitSyncScript
76ec746341 Require Statement Nits 2013-06-24 16:16:38 -07:00
CommitSyncScript
14102e8a48 Fix isMounted() for composite components
This behavior seemed incorrect for composite components.

- isComponentMounted() represents ReactComponent's lifecycle of mounting
- isMounted() represents ReactCompositeComponent's lifecycle of isMounted()

Therefore, ReactComponents no longer have isMounted(). I think this is fine since it was not supposed to be public anyway.
2013-06-24 16:15:49 -07:00
CommitSyncScript
4f2d8dfe72 Dynamic Input Component
Summary:
This is a proposal based loosely on the discussions I've had with @paulshen and @jwalke. It implements a shim for `React.DOM.input` (used as `<input>`) that supports two different use cases depending on whether `value` is provided or not.

If a `value` is //not// provided, the input will be initialized with the empty string (or `defaultValue`) and anytime the user changes the input, the `onChange` (or `onTextChange`) handler will be fired and the DOM will reflect the new changes.

  React.renderComponent(
    <input type="text" defaultValue="Untitled" onTextChange={handleChange} />,
    container
  );

If a `value` is provided, the input will be initialized to that value. Anytime the user changes the input, the `onChange` (or `onTextChange`) handler will be fired. However, the DOM will //not// reflect the new changes. If a `value` is provided, it is the responsibility of the owner to update the `value` prop passed in.

  var value = "Untitled";
  var input = React.renderComponent(
    <input type="text" value={value} onTextChange={handleChange} />,
    container
  );
  function handleChange(event) {
    // Do something cool like strip out non-numbers.
    var value = event.target.value.replace(/\D/g, '');
    input.setProps({value: value});
  }

This is just a start and we should build similar components for `textarea` and `select`. Also, this does not inject the new components because the changes are not backward compatible. Once we change all `<input>` uses to use `ReactDOMInput`, then we can inject.
2013-06-24 16:13:14 -07:00
CommitSyncScript
7c60bb3e54 Add unit test to prevent regression
The original autobinding diff made some assumptions about how methods were called on components that had to be reverted. This diff
enforces those assumptions in a test
2013-06-24 16:12:06 -07:00
CommitSyncScript
a62686622b Clean up naming
Fix a small style nit
2013-06-24 16:11:55 -07:00
CommitSyncScript
a61f4df0b9 Fix a bunch of problems with implicit autobinding 2013-06-24 16:11:24 -07:00
CommitSyncScript
3266818b42 fix bad caching in ReactID.getID 2013-06-24 16:11:00 -07:00
CommitSyncScript
c9ecbaccb3 Use React.autoBind by default.
Per our discussion - this is the general approach we'd like to take for
the public facing API.

    var MyComponent = React.createClass({
      render: function() {
        return <div onClick={this.myCallback} />;
      },
      myCallback: function() {
      }
    });
2013-06-24 16:10:33 -07:00
Ben Newman
336a0facc1 Merge pull request #99 from petehunt/invariants
Remove second argument to invariant()
2013-06-24 15:38:22 -07:00
petehunt
7053f59ad1 Remove second argument to invariant() 2013-06-23 21:58:59 -07:00
CommitSyncScript
3373572e15 Use ReactID.{get,set}ID instead of manipulating .id property directly.
Another step in the plan to centralize control of React-specific identifers.
2013-06-21 16:08:07 -07:00
CommitSyncScript
1839bcf109 Replace uses of ReactDOMNodeCache and getDOMNodeID with ReactID.
Also removed some unnecessary calls to `document.getElementById`, which
will eventually cease to work for React-specific IDs.

This clears the way for the deprecation of `ReactDOMNodeCache` and
`getDOMNodeID`.
2013-06-21 16:07:51 -07:00
CommitSyncScript
191c0dec32 Consolidate ReactDOMNodeCache and getDOMNodeID into ReactID.
When we move away from using the "id" attribute to identify
React-generated elements, we will need the cache (formerly
ReactDOMNodeCache) to be tied much more closely to the code that looks
elements up by ID (getDOMNodeID) and sets element IDs, since the magic of
document.getElementById will no longer be available.

The priming functions are going to come in handy when we create new DOM
fragments in mountComponent.

For backwards compatibility, the ReactDOMNodeCache and getDOMNodeID
modules still exist, but they are implemented entirely in terms of
functions exported from ReactID.
2013-06-21 16:07:22 -07:00
CommitSyncScript
2bc2b52eaa Allow accessing siblings by ref
This makes it possible to write a wider range of components without
depending on internal implementations.
2013-06-21 16:02:57 -07:00
CommitSyncScript
36a724feca Add reactComponentExpect#toBeComponentOfType
This adds a `toBeComponentOfType` method to `reactComponentExpect`. Now that we are injecting composite native components, `toBeDOMComponentWithTag` will not suffice and should be deprecated.
2013-06-21 16:02:55 -07:00
Paul O’Shannessy
18352090e7 Blogpost & changelog for v0.3.3 2013-06-21 10:57:41 -07:00
Paul O’Shannessy
4c97ffee34 Bring in Facebook's requestAnimationFrame module 2013-06-20 16:51:21 -07:00
Ben Alpert
ac5320e887 Remove textContent tests; they break in phantomjs 2013-06-19 17:26:29 -07:00
Ben Newman
91b10bd37c Merge pull request #109 from zpao/esprima-dep-as-tarball
Use github tarball link for esprima dependency
2013-06-19 15:59:56 -07:00
petehunt
79a2734068 Merge pull request #113 from vjeux/community_2
Community Roundup #2
2013-06-19 12:19:40 -07:00
Vjeux
32030687ba Community Roundup #2 2013-06-19 21:18:28 +02:00
Paul O’Shannessy
cb01363260 Cleanup lint warnings 2013-06-19 11:06:04 -07:00
Ben Alpert
0493b27222 Don't update value or textContent unnecessarily
_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.
2013-06-18 23:31:15 -07:00
Ben Alpert
44d6b94752 Merge remote-tracking branch 'origin/master' into textarea-update-value 2013-06-18 23:31:08 -07:00
petehunt
a2bc7387e4 Merge pull request #110 from vjeux/patch-1
Fix dangerouslySetInnerHTML
2013-06-18 23:13:58 -07:00
Christopher Chedeau
ea5e13893e Fix dangerouslySetInnerHTML 2013-06-19 00:09:02 -06:00
Paul O’Shannessy
bd044fc919 Use github tarball link for esprima dependency
It turns out that (at least for local development) npm has a long
standing bug where it doesn't recognize changing dependencies stored as
git urls (see https://github.com/isaacs/npm/issues/1727). Luckily npm
understand tarballs and GitHub provides tarballs for every commit, so
the workaround is easy, though unfortunate.
2013-06-18 15:29:37 -07:00
Paul O’Shannessy
870a29d9b0 Use absolute URLs for FB comments box 2013-06-18 10:20:34 -07:00
CommitSyncScript
0e91febb9c Better warnings for missing keys on arrays
We have less dynamic arrays in the code base now so let's start warning for all
the cases where we pass dynamic arrays without keys.

I use the displayName to point out which component's render method was
responsible. I only warn once per component. If the child was created in a
different component (and passed as a property) I also show the owner of the
child. Maybe it should've attached the key at a higher level.

This does give false positives for arrays that are truly static. Those should
probably be refactored to use the XML syntax if possible.
2013-06-18 09:36:24 -07:00
CommitSyncScript
37ddfa0521 Don't transfer children in transferPropsTo
06cff60bc1 made it so that `this.props.children` was no longer set when
none were provided.

  var x = <div />;

This caused an issue if the code was relying on the following not
transferring children.

  return this.transferPropsTo(<div />);
  // this now transfer children
2013-06-18 09:32:23 -07:00
Paul O’Shannessy
5bd449c157 Ignore .module-cache directories 2013-06-17 17:14:14 -07:00
Paul O’Shannessy
b69d7f0d2a Merge pull request #107 from remixz/package-json-fix
Fix package.json's reference to vendor/constants.js
2013-06-17 16:31:24 -07:00
Zach Bruggeman
a0475b3c29 Fix package.json's reference to vendor/constants.js 2013-06-17 16:27:07 -07:00
CommitSyncScript
3156458041 Fix most lint warnings/errors 2013-06-17 16:26:56 -07:00
CommitSyncScript
48333acba6 Remove reactKeys
It wasn't being used and it wasn't conforming to the @providesModule === file name rule.
2013-06-17 16:26:45 -07:00
CommitSyncScript
4a0456fb8e Fix lint warning about mismatched file & module name
@providesModule should match the file name. In this case we'll be consistent and suffix mixins with Mixin.
2013-06-17 16:26:07 -07:00
petehunt
405be0f966 Merge pull request #106 from benjamn/rename-woodchipper
Rename Woodchipper to ConstantVisitor, and simplify the vendor/constants.js interface
2013-06-17 15:55:25 -07:00
Ben Newman
000928f9dc Use recast.parse and .print for require("vendor/constants").propagate.
This removes the need to pass a callback, which is a nice improvement.
2013-06-17 16:30:32 -04:00
Ben Newman
0f87e8ee87 Rename Woodchipper-related stuff.
No functional changes introduced by this commit.

Renamed:

    woodchipper.js -> constants.js
    Woodchipper -> ConstantVisitor
    debranch -> propagate
2013-06-17 16:21:51 -04:00
Paul O’Shannessy
2195a479a8 Merge pull request #105 from benjamn/fix-options.writeback-bug
Upgrade Recast to 0.4.8 to fix options.writeback bug
2013-06-17 13:07:43 -07:00
Ben Newman
2383fd8813 Upgrade Recast to 0.4.8 to fix options.writeback bug.
Bug introduced by: https://github.com/benjamn/recast/commit/e913b22f8f
Bug fixed by: https://github.com/benjamn/recast/commit/170e18091e
2013-06-17 15:57:33 -04:00
CommitSyncScript
d8b6d260c9 Add missing license header 2013-06-17 12:50:55 -07:00
CommitSyncScript
6a41ede2d4 Fixing known keying problems
This fixes the last known parts of the flattening experiment. This has grown to
be somewhat complex and potentially fragile because of it. We may end up
reverting flattening in the future or address it slightly differently.

The purpose of this diff is to test if we've finally understood the real world
edge cases that flattening can lead to and how we have to key components to
cover those cases.

With this commit we never rekey the internal _key property. The semantics is
that once a component passes through a composite component, it's identity is
frozen.

props.key should accept numeric values and booleans which includes 0 and false.
This fixes the truthiness check.

We should never warn about missing key properties if a component is passed as a
static child. The _key acts as a flag to determine whether this component
was checked already.
2013-06-17 12:50:29 -07:00
CommitSyncScript
c04081bc56 Add missing license header. 2013-06-17 12:50:15 -07:00
CommitSyncScript
0b1ecd8872 Add Back DOMCharacterDataModified
Accidentally lost `DOMCharacterDataModified`.
2013-06-17 12:49:58 -07:00
CommitSyncScript
88e90d5601 Use Synthetic Events
Swaps out usage of `AbstractEvent` with `SyntheticEvent` (and subclasses).
2013-06-17 12:49:45 -07:00
CommitSyncScript
03464dc148 Fix EventPluginRegistry Unit Tests in GitHub
Dumping the mock cache isn't dirying the modules in the open source version, so we have to unit test a different way. If we can fix the unit test framework, we should revert this.

Also, I added strict mode to `EventPluginRegistry.js`.

See: https://github.com/facebook/react/pull/91
2013-06-17 12:49:22 -07:00
CommitSyncScript
1112f1a003 React onlyChild utility.
Small utility that extracts and validates that there is only a single
child passed to a React composite component. The benefit here is that we
abstract away *how* the children are actually stored while we iterate on
different approaches. This way we won't break callsites as we try different
ideas. When we settle on a final approach, all of these callsites will still
work.
2013-06-17 12:48:47 -07:00
Paul O’Shannessy
c1576fcf97 Remove trailing whitespace 2013-06-17 12:48:43 -07:00
CommitSyncScript
8592eacbf9 Use Node Cache in ReactComponent
There are currently two places where we lookup and cache nodes: `ReactDOMNodeCache` and `ReactComponent`. Instead, we should just consolidate caches and make `ReactComponent` use `ReactDOMNodeCache`.
2013-06-17 12:48:26 -07:00
petehunt
888cb824d7 Merge pull request #103 from benjamn/fix-maxBuffer-exceeded
Use grunt.util.spawn for jsx:* tasks instead of exec
2013-06-17 11:10:07 -07:00
petehunt
061527df6c Merge pull request #102 from zpao/fix-transformer
Improve JSXTransformer
2013-06-17 11:08:35 -07:00
Ben Newman
96b0a0253f Use grunt.util.spawn for jsx:* tasks instead of exec.
This should prevent "Warning: stdout maxBuffer exceeded" errors.

Also piping child process stdout and stderr to the parent process, so
you can see more of what's happening during the build process.
2013-06-17 13:56:58 -04:00
Paul O’Shannessy
c79a59b599 Improve JSXTransformer
The biggest improvement is that we'll now insert each parsed JSX script
back into a `<script>` tag with the body set. This allows the browser to
execute these scripts normally. Using `Function(functionBody)` or
`eval(functionBody)` both execute in window scope, but `var` assignments
don't actually get set on window (unlike everywhere else).

I also did some cleanup to make the code a little bit more readable.
In my minimal test cases this didn't break anything (scripts loaded in
the right order).
2013-06-17 10:52:16 -07:00
petehunt
fad7d58fc9 Update jsx-is-not-html.md 2013-06-17 03:11:57 -06:00
petehunt
97efa84676 Merge pull request #88 from vjeux/jsx_pitfall
Adding JSX pitfalls section in the docs
2013-06-17 02:05:55 -07:00
petehunt
22347ea54d Merge pull request #95 from groodt/patch-1
Small correction to tutorial.md
2013-06-17 01:36:51 -07:00
petehunt
46513c6d78 Merge pull request #96 from groodt/master
Very minor corrections to documentation
2013-06-17 01:26:31 -07:00
petehunt
1c7d01c2f4 Merge pull request #97 from spicyj/patch-1
Link to my own blog instead of Quora
2013-06-17 01:25:31 -07:00
ngavalas
7a0f2d71bb Add callbacks to all public-facing state/props methods
All public facing {set,replace,force}{props,state} methods now support
callbacks.
2013-06-16 22:45:36 -07:00
Ben Alpert
4104beadbb Link to my own blog 2013-06-15 18:59:45 -06:00
Greg Roodt
87f4b8be67 Minor grammar. 2013-06-15 11:08:06 +03:00
Greg Roodt
8d729d7da2 Minor typo. 2013-06-15 11:07:20 +03:00
Greg Roodt
f016479289 Update tutorial.md
The ajax call happens every 5 seconds, not every 60 seconds.
2013-06-15 10:15:42 +03:00
ngavalas
c81cc2e6d5 markdown syntax
Small problem with markdown syntax in syntax-highlighted block.
2013-06-14 16:41:02 -07:00
ngavalas
f3aac85d01 Updated docs and check for truthiness
Change api docs to reflect presence of the new argument.  In addition,
callback was change to require only a "truthy" value.
2013-06-14 16:37:20 -07:00
ngavalas
c6665e3460 Adds optional callback to setState
This commit adds an optional callback as a second argument to
`setState`, to be called after `setState` runs.

We never guarantee synchronous execution of `setState`, and as per
@phunt, we don't want to make that guarantee because we may eventually
batch calls to `setState`.  @jwalke agrees with him.
2013-06-14 16:23:06 -07:00
Timothy Yung
c7295b9e09 Merge pull request #61 from spicyj/getdomnodeid
getDOMNodeID: Don't return .id on random objects
2013-06-14 11:27:44 -07:00
Ben Newman
9fd9f712bf Merge pull request #91 from yungsters/master
Fix `EventPluginRegistry` Unit Tests
2013-06-14 06:45:57 -07:00
Ben Alpert
a9c70bcc1c getDOMNodeID: Don't return .id on random objects
If you defined a global named `id` (a horrible name, I know) then
getDOMNodeID(window) would return that object. Since only DOM nodes can
have IDs, this should be a noop change otherwise.

Test Plan:
Verified that document.documentElement and document.body both support
getAttributeNode properly in latest Chrome and in IE8.
2013-06-14 00:21:15 -07:00
Timothy Yung
80edd6ca87 Merge pull request #89 from vjeux/patch-4
Exposing ReactProps as React.Props
2013-06-13 21:08:10 -07:00
Timothy Yung
48f46b568d Merge pull request #92 from spicyj/fix-textchange
Fix textchange event enqueueing
2013-06-13 20:53:47 -07:00
yungsters
279792f891 Fix EventPluginRegistry Unit Tests
Dumping the mock cache isn't dirying the modules, so we have to unit test a different way. If we can fix our unit test framework, we should revert this.

Also, I added strict mode to `EventPluginRegistry.js`.
2013-06-13 20:38:23 -07:00
Ben Alpert
792b69ba11 Fix textchange event enqueueing
(Was broken by e1535fbd71d8c89c82cd9d9073c1ee97ee6a3b00.)
2013-06-13 20:34:14 -07:00
Paul O’Shannessy
06cff60bc1 Sync latest JSX transform - all children passed as separate arguments
This was a part of e1fe13d0cb upstream.
2013-06-13 18:18:54 -07:00
CommitSyncScript
770ec5946a Unbreaking falsy check on style values
Style values can be the number zero which is an actual value. So we check for
null instead. The empty string case falls through.
2013-06-13 17:49:04 -07:00
CommitSyncScript
b525a0c061 Unnecessary this._rootNodeID Invariant
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.)
2013-06-13 17:48:09 -07:00
CommitSyncScript
34970fd785 Fix tht typos 2013-06-13 17:47:59 -07:00
Paul O’Shannessy
ceb5303581 Merge pull 75 upstream
Needed to make some small changes since we weren't synced when it was
merged.
2013-06-13 17:47:51 -07:00
CommitSyncScript
f456f8fa8d Remove isStatic
This can be replicated with a custom component that always returns false`
from `shouldComponentUpdate`. A generic implementation might look like:

```
var StaticContainer = React.createClass({
  shouldComponentUpdate: function() {
    return false;
  },

  render: function() {
    return this.transferPropsTo(this.props.children[0]);
  }
});
```

And then used in JSX as
`<StaticContainer><div>Hello!</div></StaticContainer>`, resulting in
only `<div>Hello!</div>` being inserted into the DOM.
2013-06-13 17:40:52 -07:00
CommitSyncScript
e1fe13d0cb Pass multiple children in JSX as additional arguments
This is an alternative to D809298. In normal usage you'd end up with a single
flat array in props.children.
2013-06-13 17:40:05 -07:00
CommitSyncScript
01511ea557 Remove Unnecessary DOM Mutations
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.
2013-06-13 17:39:47 -07:00
CommitSyncScript
802241a660 Cleanup style Prop Reconciliation
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.
2013-06-13 17:39:23 -07:00
CommitSyncScript
e1535fbd71 Create EventPluginRegistry
The `EventPluginHub` module was getting huge and scary. This pulls out all of the logic required to inject plugins and publish their event registration names into a new `EventPluginRegistry` module.

Functionally, nothing should have changed. I added many error checks to cover edge cases that we were not yet running into, but they are all in `EventPluginRegistry` and unit tested.
2013-06-13 17:38:49 -07:00
CommitSyncScript
aea8e16b4a Add ReactComponent#isMounted
There is currently no way for components to know whether or not they are mounted. This means there's no way for callbacks to figure out if they can make certain assumptions (e.g. can `getDOMNode()` or `setState()` be safely invoked).

This adds an `isMounted` protected method that lets components properly handle callback behavior when unmounted.
2013-06-13 17:38:20 -07:00
CommitSyncScript
0d6bb650cb Add HTML5 Drag/Drop events to React 2013-06-13 17:37:58 -07:00
Ben Alpert
ddc4ffffa0 Don't let textarea value change via textContent
Turns out my tests before weren't particularly useful because
receiveProps doesn't end up running componentDidUpdate since the
transaction never finishes. Now they use replaceProps instead (and I
verified that commenting out the "rootNode.value = ..." line makes the
tests fail, which wasn't true before).
2013-06-12 19:12:52 -07:00
Vjeux
d7a5f137ff Adding JSX pitfalls section in the docs 2013-06-13 03:08:50 +02:00
Christopher Chedeau
34173638d4 Exposing ReactProps as React.Props
Right now, even though ReactProps is committed, there is no way to use it as it is not exposed.
2013-06-12 18:28:25 -06:00
Timothy Yung
c9618587ef Merge pull request #67 from vjeux/fb_comments
Facebook comments integration on Docs and Blog
2013-06-12 16:13:05 -07:00
Vjeux
101bfa3112 Facebook comments integration on Docs and Blog 2013-06-13 01:08:31 +02:00
Vjeux
523bde4dc5 Community round-up blog post 2013-06-12 13:55:51 -07:00
Jordan W
483350905b Merge pull request #86 from petehunt/fixed-width-height
Fixed width/height on React logo
2013-06-12 12:35:33 -07:00
petehunt
5b72334852 Fixed width/height on React logo 2013-06-12 12:33:38 -07:00
Ben Alpert
d13a37ce22 Don't set value if it'll be a noop 2013-06-12 01:55:26 -07:00
Ben Alpert
0c6f4b3bcc Set textarea value when changing content
At http://jsfiddle.net/spicyj/W4QLq/, typing into the textbox would cause
clicking the button to do nothing; now it should work.
2013-06-12 00:14:58 -07:00
Timothy Yung
0e6fca4a38 Merge pull request #68 from vjeux/twitter
Integrate twitter in the support page
2013-06-11 22:34:44 -07:00
Timothy Yung
dfd76be568 Merge pull request #75 from spicyj/textchange
Add new textChange event: input + IE shim
2013-06-11 22:00:19 -07:00
Ben Alpert
0dc08c2115 Missing semicolon 2013-06-11 21:54:50 -07:00
Ben Alpert
6b572b3f25 Refactor based on review feedback 2013-06-11 21:51:47 -07:00
Timothy Yung
c6c40a5fb3 Merge pull request #84 from spicyj/remove-props
Correctly remove attributes when deleting props
2013-06-11 19:54:36 -07:00
Ben Alpert
fdc6beed1a Fix nit and comment 2013-06-11 19:53:24 -07:00
Ben Alpert
731aa8ead1 internalPropNames isn't necessary, so remove it
Perhaps we'll bring it back as a future perf optimization if that
appears useful.
2013-06-11 19:02:35 -07:00
Ben Alpert
8762634cf1 Remove unused require 2013-06-11 17:28:06 -07:00
Ben Alpert
735a91c9d5 Fix relative require copy pasta 2013-06-11 17:24:35 -07:00
Ben Alpert
705ce56694 Correctly remove attributes when deleting props
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.
2013-06-11 16:46:10 -07:00
petehunt
94fdf2cf5d Merge pull request #81 from benjamn/issue-80-commoner-no-longer-rewrites-require
Upgrade Commoner and Recast to latest versions
2013-06-11 16:22:17 -07:00
Paul O’Shannessy
758c21fb9c Merge pull request #77 from spicyj/componentdidmount-order
Run inner componentDidMount method first
2013-06-11 13:10:03 -07:00
Ben Newman
15360056bd Upgrade Commoner and Recast to latest versions.
The Commoner upgrade is a big one because it makes bin/jsx no longer
rewrite module identifiers to be relative by default, which should
reduce confusion for people trying to use it as a standalone
transformer.

Closes #80.
2013-06-11 15:24:50 -04:00
Ben Alpert
932c45a7ab Check canUseDOM before calling isEventSupported 2013-06-10 18:27:22 -07:00
Ben Alpert
e39743f2f8 Removed unused variable 2013-06-10 17:19:45 -07:00
Paul O’Shannessy
cff4d53a9e Add missing license headers 2013-06-10 16:25:37 -07:00
CommitSyncScript
c2ce1d00cd Fix Event Normalization in IE<9
IE<9 relied on the `target` property being overriden. This adds back a hack that only applies in IE<9. I'll be able to revert this hack once I check in synthetic events.
2013-06-10 16:19:25 -07:00
CommitSyncScript
b3e0dc47a8 Rename "Delegate" to "Synthetic"
This is just a simple rename of the event classes to `SyntheticEvent`. I've also updated the constructor arguments to be more correct:

 - `dispatchConfig`: Data used by the plugin system for dispatching the event, for example: `{registrationName: 'onClick'}`
 - `dispatchMarker`: An identifying marker used to describe where the event is occuring, for example: `.reactRoot[0]`
2013-06-10 16:19:03 -07:00
CommitSyncScript
93f979ae18 Use isAncestorIDOf in ReactInstanceHandles
This is both a functional fix and performance fix for `ReactInstanceHandles`.

 - `getFirstReactDOM` uses `indexOf` but should be checking ancestry (via `isMarker()`).
 - Added `isAncestorIDOf`, checking ancestry can be way faster than getting a common ancestor: http://jsperf.com/react-ancestor-id-check
2013-06-10 16:18:40 -07:00
CommitSyncScript
ca3f871646 Reduce ReactInstanceHandles API Surface Area
Change `ReactInstanceHandles` so that `getFirstCommonAncestorID` and `nextDescendantID` are now private (and documented to be only exposed for unit testing). Also:

 - Renamed `nextDescendantID` to `getNextDescendantID`.
 - Renamed `parentID` to `getParentID`.

I also organized `ReactInstanceHandles-test` by method names.

Functionally, this diff should not change anything.
2013-06-10 16:18:13 -07:00
CommitSyncScript
1be6c592a6 Fix typechecks for isRenderedByReact() 2013-06-10 16:17:52 -07:00
CommitSyncScript
37cde3d864 Stabilize minimal server rendering API
This is a pretty killer feature and the API is simple. I know it's another API method on React, but I think it's
the only way.
2013-06-10 16:17:26 -07:00
Ben Alpert
2afd7186ae Run inner componentDidMount method first
Fixes #76.
2013-06-09 17:21:22 -07:00
Ben Alpert
c19bf9cffe Add new textChange event: input + IE shim
IE8 doesn't support oninput and IE9 supports it badly but we can do
almost a perfect shim by listening to a handful of different events
(focus, blur, propertychange, selectionchange, keyup, keydown).

This always triggers event handlers during the browser's event loop (not
later in a setTimeout) and after the value property has been updated.

The only case I know of where this doesn't fire the event immediately is
if (in IE8) you modify the input value using JS and then the user does a
key repeat, in which case we fire the event on the second keydown.

Test Plan:
Modify ballmer-peak example to add es5-shim and to use onTextChange
instead of onInput. In IE8, IE9, and latest Chrome, make sure that the
event is fired upon:

* typing normally,
* backspacing,
* forward-deleting,
* cutting,
* pasting,
* context-menu deleting,
* dragging text to reorder characters.

After modifying the example to change .value, make sure that the event
is not fired as a result of the changes from JS (even when the input box
is focused).
2013-06-09 04:18:15 -07:00
CommitSyncScript
7e7579e1ba Assign the same keys if it's a single nested array or not
If you specify a single array, we didn't prefix the keys with 0.

If you later add children, the first array won't have the same key.
2013-06-07 22:10:20 -07:00
CommitSyncScript
582359aeea Remove React.createComponentRenderer
Remove ReactMount.createComponentRenderer because it does not function
correctly.

  var f = React.createComponentRenderer(<div />);

  var container1 = document.createElement('div');
  var container2 = document.createElement('div');
  f(container1);
  f(container2); // error because mounting same instance into new root
2013-06-07 22:10:06 -07:00
CommitSyncScript
6c3c643c8e Fix typo in OrderedMap
Unique was spelled wrong. This fixes it.
2013-06-07 22:09:49 -07:00
CommitSyncScript
3eaed5a122 Delegate Event Classes
React's top-level event delegation dispatches `AbstractEvent` objects that contain:

 - `nativeEvent`, the original browser event.
 - `data`, an object with custom normalized properties.

This diff creates a set of `DelegateEvent` classes that will replace `AbstractEvent`. The goal is two-fold:

 # Provide a cross-browser implementation that conforms to the DOM Level 3 Events API so people don't have to use `nativeEvent`.
 # Generalize the event object API so that it can be shared by `DOMEventManager`, a top-level event delegation WIP.

This simply implements the classes. I will follow-up by replacing `AbstractEvent` with them.
2013-06-07 22:08:32 -07:00
CommitSyncScript
4bb966a7f0 Bugfixes to key assignment
Type coersion bug and ID breaking assumption.

Names need to be wrapped in something unique since otherwise two unique siblings
can end up having IDs that are subsets of eachother.
2013-06-07 22:08:14 -07:00
CommitSyncScript
0e9e64c550 Replace persistentCloneOf with persist
There are to reasons to prefer a `persist` method on the event rather than a static method:

 - In open source, people do not have access to `AbstractEvent`.
 - This will allow people to persist events without requiring another module.
 - This will make refactors easier and more flexible.
2013-06-07 22:07:43 -07:00
Jeffrey Lin
c5998fb483 Merge pull request #72 from benjamn/module-cache
Cache modules for jsx grunt tasks in react-tools/.module-cache
2013-06-07 18:11:05 -07:00
Ben Newman
880ada0a1c Cache modules for jsx grunt tasks in react-tools/.module-cache.
As of Commoner v0.6.11, the default is to put the cache files in
output/.module-cache, which used to be build/modules/.module-cache
before this commit. That still happens when you run bin/jsx directly,
just not for grunt tasks anymore.

The module cache needs to be cleared much less often than
build/modules, so it doesn't make sense to throw away all that work.
2013-06-07 18:02:43 -04:00
Jordan W
a5e5f53494 Merge pull request #69 from jordow/SimplifyExample
Make todo example shorter and not rely on the DOM.
2013-06-07 13:16:17 -07:00
jordow
81f3a5c6cd Make todo example shorter and not rely on the DOM. 2013-06-07 13:11:40 -07:00
Paul O’Shannessy
59bee8df21 Remove clowny diff.diff
An artifact of our sync process.
2013-06-06 15:09:11 -07:00
Paul O’Shannessy
796837b8c7 Merge pull request #66 from zpao/sync-latest
Sync latest from Facebook
2013-06-06 15:03:09 -07:00
CommitSyncScript
2dc24fc234 Add typecheck, cleanup
Followup with some additional comments for https://github.com/facebook/react/pull/58
2013-06-06 14:50:54 -07:00
CommitSyncScript
88923f61a7 Improve Browser Support for wheel Event
This improved browser support for the `wheel` event.

 - Try to use `wheel` event (DOM Level 3 Specification).
 - Fallback to `mousewheel` event.
 - Fallback to `DOMMouseWheel` (older Firefox).

Also, since `wheel` is the standard event name, let's use that in React.

NOTE: The tricky part was detecting if `wheel` is supported for IE9+ because `onwheel` does not exist.

Test Plan:
Execute the following in the console on a page with React:

  var React = require('React');
  React.renderComponent(React.DOM.div({
    style: {
      width: 10000,
      height: 10000
    },
    onWheel: function() {
      console.log('wheel');
    }
  }, null), document.body);

Verified that mousewheel events are logged to the console.
Verified in IE8-10, Firefox, Chrome, and Safari.
2013-06-06 14:48:25 -07:00
CommitSyncScript
ba6fea1bf5 Simplify Event Core
Summary:
This makes a few changes to React Core, most notably `ReactEventEmitter` and `ReactEventTopLevelCallback`.

 - Changed `ReactEventEmitter` to use `EventListener` (instead of `NormalizedEventListener`).
 - Deleted `NormalizedEventListener` (which was previously broken).
 - Created `getEventTarget` which is used to get a normalized `target` from a native event.
 - Changed `ReactEventTopLevelCallback` to use `getEventTarget`.
 - Renamed `abstractEventType` to `reactEventType` in `AbstractEvent`.
 - Reanmed `abstractTargetID` to `reactTargetID` in `AbstractEvent`.
 - Removed `originatingTopLevelEventType` from `AbstractEvent` (unused and violates encapsulation).
 - Removed `nativeEvent.target === window` check when refreshing authoritative scroll values (unnecessary).

This actually fixes React because `NormalizedEventListener` does not currently do what it promises to do (which is normalizing `target` on the native event). The `target` event is read-only on native events.

This also revises documentation and adds `@typechecks` to a few modules.

NOTE: Most importantly, this sets the stage for replacing `AbstractEvent` with `ReactEvent` and subclasses, piecemeal.
2013-06-06 14:48:12 -07:00
CommitSyncScript
36d8ce8fab [React] remove deprecated Component.update()
Summary: Since grepping for `update` and `updateAll` is pretty hard, I had these these functions call through but complain loudly. This noisy call through has been in prod for over a week and I haven't heard any complains, so let's take it out altogether.
2013-06-06 14:46:53 -07:00
CommitSyncScript
153fd9246e [React] Don't use autoMockOff 2013-06-06 14:29:45 -07:00
CommitSyncScript
fac24d462f React: Add @typechecks to CallbackRegistry 2013-06-06 14:29:45 -07:00
Paul O’Shannessy
83101b878e Add license headers to new files 2013-06-06 14:29:45 -07:00
CommitSyncScript
9d1055b3d2 Rename ReactEvent to ReactEventEmitter
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).
2013-06-06 14:29:45 -07:00
CommitSyncScript
0614d30654 Move test utils internally, update for consistency 2013-06-06 14:29:44 -07:00
CommitSyncScript
9965b6b9dd Fix Listener Cleanup on Unmount
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.
2013-06-06 14:29:44 -07:00
CommitSyncScript
a06de4bc4f Cleanup ReactCurrentOwner on Fatal
If a React component's render() fatals, it may contaminate
ReactCurrentOwner. This will cause the owner to be set improperly for
the next React.renderComponent() invocation (which causes an owner to be
set when there shouldn't be one).
2013-06-06 14:29:44 -07:00
CommitSyncScript
11a7cb5b73 Only Allow forceUpdate on Mounted Components 2013-06-06 14:29:44 -07:00
CommitSyncScript
bae6100ae8 Make ReactIdentity-test less fragile with respect to root IDs. 2013-06-06 14:29:44 -07:00
CommitSyncScript
3ffbb4d096 Re-add invariant
Bring back the invariant() that disallows setProps() and replaceProps()
on owned components.
2013-06-06 14:29:44 -07:00
CommitSyncScript
ca5d7bc683 Add getDefaultProps()
As it turns out, default values are very useful. This implements
getDefaultProps(), a hook for components to provide prop values when
a prop is not specified by the user.
2013-06-06 14:29:44 -07:00
CommitSyncScript
1457850b72 Rename domUtils to dom 2013-06-06 14:29:44 -07:00
CommitSyncScript
100af48f53 Support rendering different components into same node
var container = ...; // some DOM node
React.renderComponent(<div />, container);
React.renderComponent(<span />, container);

This should replace the rendered <div> with a <span>, effectively
reconciling at the root level.
2013-06-06 14:29:44 -07:00
CommitSyncScript
3e211bf662 [React] Removing invariant warning about updating owner state
It seems like it's possible to render a component that ends up having an
owner. Because you can end up rendering inside a render somehow.
2013-06-06 14:29:44 -07:00
CommitSyncScript
606d6b8fd4 Revert Object.create in NormalizedEventListener
It seems that the use of Object.create (to comply with strict mode) in
NormalizedEventListener is not happy in IE8.
2013-06-06 14:29:44 -07:00
CommitSyncScript
b581c8cfc7 Always reassign _key for every pass
Currently we're mutating _key. Mutation here is fine, but it needs to
be idempotent - which it's not. This is causing some issues.

Instead I reassign the _key every time it passes through a flattening.
This means that it's unique and stable for a single pass through a composite
component. When it's repassed another level, it loses it previous
identity and is rekeyed by it's new location.

For auto-generated keys by index, this actually means it has the same
semantics as before flattening.

For explicit keys, it has the effect that keys need to be unique at
every level. Regardless of how the key got there. Every component needs to ensure
that it doesn't combine keys from two different sources that may collide. This
is also inline with the old semantics but less intuitive in the new model.
2013-06-06 14:29:44 -07:00
CommitSyncScript
54d3134da2 Add ReactProps.func
This adds ReactProps.func so people don't need to write the
slightly-more-cryptic ReactProps.instanceOf(Function). We should have
had this all along.
2013-06-06 14:29:44 -07:00
CommitSyncScript
259392035d mapChildren
mapChilden() is similar to Array.map() and objMap() but handles deep
nested structures and follows similar rules to flattenChildren()
2013-06-06 14:29:44 -07:00
CommitSyncScript
4b81de93d3 use key="foo" for all components
flattenChildren was only using key when child.mountInContainerNode
exists, which is defined on ReactCompositeComponent, and not
ReactNativeComponent.

This uses the isValidComponent() fn to see if we should use this key.
2013-06-06 14:29:44 -07:00
CommitSyncScript
93fc188afb style prop improvements
Some improvements to how style={{x:y}} is handled in React:
* ignores null styles, rather than setting them.

Codez:

    var highlighted = false;
    <div style={{color: highlighted ? 'red' : null}} />

Before:

    <div style="color:;"></div>

After:

    <div></div>

Respects that 0 has no units.
2013-06-06 14:29:44 -07:00
CommitSyncScript
007b75f78a Flatten Children A Single Level
This expects static children as additional arguments to the constructor
and flattens any array arguments one level deep.

Component(props, child1, child2, arrayOfChildren, child3) ->
.props.children = [child1, child2, ...arrayOfChildren, child3]

This can avoid an additional heap allocation for the unflat array.

It allows you to pass nested arrays and objects like you used to. Those
aren't immediately flattened. That makes this a fairly safe change.

Passing a dynamic array without key properties will yield a warning
(once). Might consider throwing later.

Once we change the transpiler to use the new syntax, you'll end up with
a single flat array in normal usage.

This doesn't actually update the JSX transform.
2013-06-06 14:29:43 -07:00
Vjeux
0435216eb6 Using markdown instead of html 2013-06-06 08:40:24 +02:00
Vjeux
7061d2b25b Integrate twitter in the support page 2013-06-06 08:38:09 +02:00
Paul O’Shannessy
31cdb4c8a7 Merge pull request #58 from spicyj/unmount-nothrow
Make unmountAndReleaseReactRootNode not throw
2013-06-05 15:00:00 -07:00
Paul O’Shannessy
1e76d84569 Merge pull request #32 from spicyj/input
Add new onInput event
2013-06-05 14:57:25 -07:00
Ben Alpert
6012e94e50 Make unmountAndReleaseReactRootNode not throw
When there isn't any React node in the DOM, unmountAndReleaseReactRootNode
threw an exception because component was undefined. Instead, return whether we
were able to unmount the component.
2013-06-04 14:36:16 -07:00
Ben Alpert
35306fa7f5 Revert "Simulate input event" for now
This reverts commit 580e8f0dbb.
2013-06-04 01:29:12 -07:00
Ben Alpert
580e8f0dbb Simulate input event instead of relying on native
Test Plan:
With the ballmer-peak example (modified to use input), tested that the
percentage updates when adding or deleting text in the field on Chrome
and IE9. After adding es5-shim and es5-sham to the ballmer-peak page,
IE8 works properly too.
2013-06-01 16:55:19 -07:00
Ben Alpert
2467c0e651 Update examples to use onInput instead of onKeyUp
onInput has the advantage that it responds to repeated key events before
onKeyUp and is called when modifying the input without the keyboard
(such as pasting with the mouse).

Test Plan:
Opened the ballmer-peak example and docs homepage in Chrome and checked
that both examples update whenever the text is changed.
2013-05-30 18:20:34 -07:00
Ben Alpert
292dd238e7 Add new onInput event
'input' is supported in IE9+ and all other browsers according to
https://developer.mozilla.org/en-US/docs/Web/API/window.oninput

Test Plan:
Modified ballmer-peak example to use onInput instead of onKeyUp and
tested that it works properly on latest Chrome.
2013-05-30 18:20:34 -07:00
185 changed files with 10637 additions and 4421 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ node_modules
static
.grunt
_SpecRunner.html
__benchmarks__
build/
.module-cache
*.gem

66
CHANGELOG.md Normal file
View File

@@ -0,0 +1,66 @@
## 0.4.0 (July 17, 2013)
### React
* Switch from using `id` attribute to `data-reactid` to track DOM nodes. This allows you to integrate with other JS and CSS libraries more easily.
* Support for more DOM elements and attributes (e.g., `<canvas>`)
* Improved server-side rendering APIs. `React.renderComponentToString(<component>, callback)` allows you to use React on the server and generate markup which can be sent down to the browser.
* `prop` improvements: validation and default values. [Read our blog post for details...](http://facebook.github.io/react/blog/2013/07/11/react-v0-4-prop-validation-and-default-values.html)
* Support for the `key` prop, which allows for finer control over reconciliation. [Read the docs for details...](http://facebook.github.io/react/docs/multiple-components.html)
* Removed `React.autoBind`. [Read our blog post for details...](http://facebook.github.io/react/blog/2013/07/02/react-v0-4-autobind-by-default.html)
* Improvements to forms. We've written wrappers around `<input>`, `<textarea>`, `<option>`, and `<select>` in order to standardize many inconsistencies in browser implementations. This includes support for `defaultValue`, and improved implementation of the `onChange` event, and circuit completion. [Read the docs for details...](http://facebook.github.io/react/docs/forms.html)
* We've implemented an improved synthetic event system that conforms to the W3C spec.
* Updates to your component are batched now, which may result in a significantly faster re-render of components. `this.setState` now takes an optional callback as it's second parameter. If you were using `onClick={this.setState.bind(this, state)}` previously, you'll want to make sure you add a third parameter so that the event is not treated as the callback.
### JSX
* Support for comment nodes `<div>{/* this is a comment and won't be rendered */}</div>`
* Children are now transformed directly into arguments instead of being wrapped in an array
E.g. `<div><Component1/><Component2></div>` is transformed into `React.DOM.div(null, Component1(null), Component2(null))`.
Previously this would be transformed into `React.DOM.div(null, [Component1(null), Component2(null)])`.
If you were using React without JSX previously, your code should still work.
### react-tools
* Fixed a number of bugs when transforming directories
* No longer re-write `require()`s to be relative unless specified
## 0.3.3 (June 20, 2013)
### React
* Allow reusing the same DOM node to render different components. e.g. `React.renderComponent(<div/>, domNode); React.renderComponent(<span/>, domNode);` will work now.
### JSX
* Improved the in-browser transformer so that transformed scripts will execute in the expected scope. The allows components to be defined and used from separate files.
### react-tools
* Upgrade Commoner so `require` statements are no longer relativized when passing through the transformer. This was a feature needed when building React, but doesn't translate well for other consumers of `bin/jsx`.
* Upgraded our dependencies on Commoner and Recast so they use a different directory for their cache.
* Freeze our esprima dependency.
## 0.3.2 (May 31, 2013)
### JSX
* Improved compatability with other coding styles (specifically, multiple assignments with a single `var`).
### react-tools
* Switch from using the browserified build to shipping individual modules. This allows react-tools to be used with [browserify](https://github.com/substack/node-browserify).
## 0.3.1 (May 30, 2013)
### react-tools
* Fix bug in packaging resulting in broken module.
## 0.3.0 (May 29, 2013)
* Initial public release

View File

@@ -4,6 +4,7 @@ var exec = require('child_process').exec;
var jsxTask = require('./grunt/tasks/jsx');
var browserifyTask = require('./grunt/tasks/browserify');
var wrapupTask = require('./grunt/tasks/wrapup');
var populistTask = require('./grunt/tasks/populist');
var phantomTask = require('./grunt/tasks/phantom');
var npmTask = require('./grunt/tasks/npm');
var releaseTasks = require('./grunt/tasks/release');
@@ -16,6 +17,7 @@ module.exports = function(grunt) {
jsx: require('./grunt/config/jsx/jsx'),
browserify: require('./grunt/config/browserify'),
wrapup: require('./grunt/config/wrapup'),
populist: require('./grunt/config/populist'),
phantom: require('./grunt/config/phantom'),
npm: require('./grunt/config/npm'),
clean: ['./build', './*.gem', './docs/_site', './examples/shared/*.js'],
@@ -44,6 +46,8 @@ module.exports = function(grunt) {
// defines global variables instead of using require.
grunt.registerMultiTask('wrapup', wrapupTask);
grunt.registerMultiTask('populist', populistTask);
grunt.registerMultiTask('phantom', phantomTask);
grunt.registerMultiTask('npm', npmTask);
@@ -52,11 +56,10 @@ module.exports = function(grunt) {
grunt.registerTask('build:transformer', ['jsx:debug', 'browserify:transformer']);
grunt.registerTask('build:min', ['jsx:release', 'browserify:min']);
grunt.registerTask('build:test', [
'jsx:debug',
'jsx:jasmine',
'jsx:test',
'browserify:jasmine',
'browserify:test'
'populist:jasmine',
'populist:test'
]);
grunt.registerTask('test', ['build:test', 'phantom:run']);

30
bin/jsx
View File

@@ -3,7 +3,7 @@
var visitors = require('../vendor/fbtransform/visitors').transformVisitors;
var transform = require('../vendor/fbtransform/lib/transform').transform;
var debranch = require("../vendor/woodchipper").debranch;
var propagate = require("../vendor/constants").propagate;
require("commoner").resolve(function(id) {
var context = this;
@@ -25,18 +25,30 @@ require("commoner").resolve(function(id) {
}).process(function(id, source) {
var context = this;
var constants = context.config.constants || {};
// This is where JSX, ES6, etc. desugaring happens.
source = transform(visitors.react, source).code;
return context.makePromise(function(callback) {
var constants = context.config.constants || {};
// Constant propagation means removing any obviously dead code after
// replacing constant expressions with literal (boolean) values.
source = propagate(constants, source);
// Debranching means removing any obviously dead code after
// replacing constant conditional expressions with literal
// (boolean) values.
debranch(constants, source, function(source) {
callback(null, source);
if (context.config.mocking) {
// Make sure there is exactly one newline at the end of the module.
source = source.replace(/\s+$/m, "\n");
return context.getProvidedP().then(function(idToPath) {
if (id !== "mock-modules" &&
id !== "mocks" &&
idToPath.hasOwnProperty("mock-modules")) {
return source + '\nrequire("mock-modules").register(' +
JSON.stringify(id) + ', module);\n';
}
return source;
});
});
}
return source;
});

View File

@@ -1,17 +1,53 @@
---
markdown: redcarpet
name: React
description: A JavaScript library for building user interfaces
redcarpet:
extensions:
- fenced_code_blocks
react_version: 0.3.3
pygments: true
baseurl: /react
url: http://facebook.github.io
permalink: /blog/:year/:month/:day/:title.html
exclude:
- Gemfile
- Gemfile.lock
- README.md
- Rakefile
url: http://facebook.github.io
baseurl: /react
permalink: /blog/:year/:month/:day/:title.html
redcarpet:
extensions:
- fenced_code_blocks
pygments: true
name: React
markdown: redcarpet
react_version: 0.4.0
description: A JavaScript library for building user interfaces
relative_permalinks: true
nav_docs_sections:
- title: Quick Start
items:
- id: getting-started
title: Getting Started
- id: tutorial
title: Tutorial
- title: Guides
items:
- id: why-react
title: Why React?
- id: displaying-data
title: Displaying Data
subitems:
- id: jsx-in-depth
title: JSX in Depth
- id: jsx-gotchas
title: JSX Gotchas
- id: interactivity-and-dynamic-uis
title: Interactivity and Dynamic UIs
- id: multiple-components
title: Multiple Components
- id: reusable-components
title: Reusable Components
- id: forms
title: Forms
- id: working-with-the-browser
title: Working With the Browser
subitems:
- id: more-about-refs
title: More About Refs
- id: tooling-integration
title: Tooling integration
- id: reference
title: Reference

View File

@@ -206,6 +206,9 @@ li {
list-style: none;
margin: 0;
}
ul ul {
margin-left: 20px;
}
li {
margin: 0;
}

View File

@@ -1,31 +1,27 @@
<div class="nav-docs">
<div class="nav-docs-section">
<h3>Getting started</h3>
<ul>
<li><a href="/react/docs/getting-started.html"{% if page.id == 'docs-getting-started' %} class="active"{% endif %}>Getting Started</a></li>
<li><a href="/react/docs/tutorial.html"{% if page.id == 'docs-tutorial' %} class="active"{% endif %}>Tutorial</a></li>
<li><a href="/react/docs/common-questions.html"{% if page.id == 'docs-common-questions' %} class="active"{% endif %}>Common Questions</a></li>
</ul>
</div>
<div class="nav-docs-section">
<h3>Reference</h3>
<ul>
<li><a href="/react/docs/syntax.html"{% if page.id == 'docs-syntax' %} class="active"{% endif %}>JSX Syntax</a></li>
<li><a href="/react/docs/component-basics.html"{% if page.id == 'docs-component-basics' %} class="active"{% endif %}>Component Basics</a></li>
<li><a href="/react/docs/component-data.html"{% if page.id == 'docs-component-data' %} class="active"{% endif %}>Component Data</a></li>
<li><a href="/react/docs/component-lifecycle.html"{% if page.id == 'docs-component-lifecycle' %} class="active"{% endif %}>Component Lifecycle</a></li>
<li><a href="/react/docs/event-handling.html"{% if page.id == 'docs-event-handling' %} class="active"{% endif %}>Event Handling</a></li>
<li><a href="/react/docs/advanced-components.html"{% if page.id == 'docs-advanced-components' %} class="active"{% endif %}>Advanced Components</a></li>
<li><a href="/react/docs/mixins.html"{% if page.id == 'docs-mixins' %} class="active"{% endif %}>Mixins</a></li>
<li><a href="/react/docs/api.html"{% if page.id == 'docs-api' %} class="active"{% endif %}>API</a></li>
</ul>
</div>
<div class="nav-docs-section">
<h3>Appendix</h3>
<ul>
<li><a href="/react/docs/jsx-is-not-html.html"{% if page.id == 'docs-jsx-is-not-html' %} class="active"{% endif %}>JSX is not HTML</a></li>
</ul>
</div>
{% for section in site.nav_docs_sections %}
<div class="nav-docs-section">
<h3>{{ section.title }}</h3>
<ul>
{% for item in section.items %}
<li>
<a href="/react/docs/{{ item.id }}.html"{% if page.id == item.id %} class="active"{% endif %}>
{{ item.title }}
</a>
{% if item.subitems %}
<ul>
{% for subitem in item.subitems %}
<li>
<a href="/react/docs/{{ subitem.id }}.html"{% if page.id == subitem.id %} class="active"{% endif %}>
{{ subitem.title }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>

View File

@@ -11,14 +11,14 @@ var MarkdownEditor = React.createClass({\n\
getInitialState: function() {\n\
return {value: 'Type some *markdown* here!'};\n\
},\n\
handleKeyUp: React.autoBind(function() {\n\
handleInput: function() {\n\
this.setState({value: this.refs.textarea.getDOMNode().value});\n\
}),\n\
},\n\
render: function() {\n\
return (\n\
<div className=\"MarkdownEditor\">\n\
<h3>Input</h3>\n\
<textarea onKeyUp={this.handleKeyUp} ref=\"textarea\">\n\
<textarea onInput={this.handleInput} ref=\"textarea\">\n\
{this.state.value}\n\
</textarea>\n\
<h3>Output</h3>\n\

View File

@@ -7,9 +7,9 @@ var Timer = React.createClass({\n\
getInitialState: function() {\n\
return {secondsElapsed: 0};\n\
},\n\
tick: React.autoBind(function() {\n\
tick: function() {\n\
this.setState({secondsElapsed: this.state.secondsElapsed + 1});\n\
}),\n\
},\n\
componentDidMount: function() {\n\
setInterval(this.tick, 1000);\n\
},\n\

View File

@@ -0,0 +1,40 @@
---
title: "React v0.4.0"
layout: post
author: Paul O'Shannessy
---
Over the past 2 months we've been taking feedback and working hard to make React even better. We've also added several features that awe didn't finish in time for the v0.3 launch, and are proud to announce the availability of React v0.4 today!
This release could not have happened without the support of our growing community. Since launch day, the community has contributed blog posts, questions to the [Google Group](http://groups.google.com/group/reactjs), and issues and pull requests on GitHub. We've had contributions ranging from documentation improvements to major changes to React's rendering. We've seen people integrate React into the tools they're using and the products they're building, and we're all very excited to see what our budding community builds next!
React v0.4 has some big changes. We've also restructured the documentation to better communicate how to use React. We've summarized the changes below and linked to documentation where we think it will be especially useful.
When you're ready, [go download it](/react/downloads.html)!
### React
* Switch from using `id` attribute to `data-reactid` to track DOM nodes. This allows you to integrate with other JS and CSS libraries more easily.
* Support for more DOM elements and attributes (e.g., `<canvas>`)
* Improved server-side rendering APIs. `React.renderComponentToString(<component>, callback)` allows you to use React on the server and generate markup which can be sent down to the browser.
* `prop` improvements: validation and default values. [Read our blog post for details...](http://facebook.github.io/react/blog/2013/07/11/react-v0-4-prop-validation-and-default-values.html)
* Support for the `key` prop, which allows for finer control over reconciliation. [Read the docs for details...](http://facebook.github.io/react/docs/multiple-components.html)
* Removed `React.autoBind`. [Read our blog post for details...](http://facebook.github.io/react/blog/2013/07/02/react-v0-4-autobind-by-default.html)
* Improvements to forms. We've written wrappers around `<input>`, `<textarea>`, `<option>`, and `<select>` in order to standardize many inconsistencies in browser implementations. This includes support for `defaultValue`, and improved implementation of the `onChange` event, and circuit completion. [Read the docs for details...](http://facebook.github.io/react/docs/forms.html)
* We've implemented an improved synthetic event system that conforms to the W3C spec.
* Updates to your component are batched now, which may result in a significantly faster re-render of components. `this.setState` now takes an optional callback as it's second parameter. If you were using `onClick={this.setState.bind(this, state)}` previously, you'll want to make sure you add a third parameter so that the event is not treated as the callback.
### JSX
* Support for comment nodes `<div>{/* this is a comment and won't be rendered */}</div>`
* Children are now transformed directly into arguments instead of being wrapped in an array
E.g. `<div><Component1/><Component2></div>` is transformed into `React.DOM.div(null, Component1(null), Component2(null))`.
Previously this would be transformed into `React.DOM.div(null, [Component1(null), Component2(null)])`.
If you were using React without JSX previously, your code should still work.
### react-tools
* Fixed a number of bugs when transforming directories
* No longer re-write `require()`s to be relative unless specified

30
docs/docs/01-why-react.md Normal file
View File

@@ -0,0 +1,30 @@
---
id: why-react
title: Why React?
layout: docs
permalink: why-react.html
next: displaying-data.html
---
React is a JavaScript library for creating user interfaces by Facebook and Instagram. Many people choose to think of React as the **V** in **[MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)**.
We built React to solve one problem: **building large applications with data that changes over time**. To do this, React uses two main ideas.
### Simple
Simply express how your app should look at any given point in time, and React will automatically manage all UI updates when your underlying data changes.
### Declarative
When the data changes, React conceptually hits the "refresh" button, and knows to only update the changed parts.
## Build Composable Components
React is all about building reusable components. In fact, with React the *only* thing you do is build components. Since they're so encapsulated, components make code reuse, testing, and separation of concerns easy.
## Give It Five Minutes
React challenges a lot of conventional wisdom, and at first glance some of the ideas may seem crazy. [Give it five minutes](http://37signals.com/svn/posts/3124-give-it-five-minutes) while reading this guide; those crazy ideas have worked for building thousands of components both inside and outside of Facebook and Instagram.
## Learn More
You can learn more about our motivations behind building React in [this blog post](http://facebook.github.io/react/blog/2013/06/05/why-react.html).

View File

@@ -0,0 +1,91 @@
---
id: displaying-data
title: Displaying Data
layout: docs
permalink: displaying-data.html
prev: why-react.html
next: jsx-in-depth.html
---
The most basic thing you can do with a UI is display some data. React makes it easy to display data and automatically keeps the interface up-to-date when the data changes.
## Getting Started
Let's look at a really simple example. Create a `hello-react.html` file with the following code:
```html
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<script src="http://fb.me/react-{{site.react_version}}.min.js"></script>
<script src="http://fb.me/JSXTransformer-{{site.react_version}}.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/jsx">
// ** Your code goes here! **
</script>
</body>
</html>
```
For the rest of the documentation, we'll just focus on the JavaScript code and assume it's inserted into a template like the one above. Replace the placeholder comment above with the following JS:
```javascript
/** @jsx React.DOM */
var HelloWorld = React.createClass({
render: function() {
return (
<p>
Hello, <input type="text" placeholder="Your name here" />!
It is {this.props.date.toTimeString()}
</p>
);
}
});
setInterval(function() {
React.renderComponent(
<HelloWorld date={new Date()} />,
document.getElementById('example')
);
}, 500);
```
## Reactive Updates
Open `hello-react.html` in a web browser and type your name into the text field. Notice that React is only changing the time string in the UI — any input you put in the text field remains, even though you haven't written any code to manage this behavior. React figures it out for you and does the right thing.
The way we are able to figure this out is that React does not manipulate the DOM unless it needs to. **It uses a fast, internal mock DOM to perform diffs and computes the most efficient DOM mutation for you.**
The inputs to this component are called `props` — short for "properties". They're passed as attributes in JSX syntax. You should think of these as immutable within the component, that is, **never write to `this.props`**.
## Components are Just Like Functions
React components are very simple. You can think of them as simple function that take in `props` and `state` (discussed later) and render HTML. Because they're so simple, it makes them very easy to reason about.
> Note:
>
> **One limitation**: React components can only render a single root node. If you want to return multiple nodes they *must* be wrapped in a single root.
## JSX Syntax
We strongly believe that components are the right way to separate concerns rather than "templates" and "display logic." We think that markup and the code that generates it are intimately tied together. Additionally, display logic is often very complex and using template languages to express it becomes cumbersome.
We've found that the best solution for this problem is to generate markup directly from the JavaScript code such that you can use all of the expressive power of a real programming language to build UIs. In order to make this easier, we've added a very simple, **optional** HTML-like syntax for the function calls that generate markup called JSX.
**JSX lets you write JavaScript function calls with HTML syntax.** To generate a link in React using pure JavaScript you'd write: `React.DOM.a({href: 'http://facebook.github.io/react/'}, 'Hello React!')`. With JSX this becomes `<a href="http://facebook.github.io/react/">Hello React!</a>`. We've found this has made building React apps easier and designers tend to prefer the syntax, but everyone has their own workflow, so **JSX is not required to use React.**
JSX is very small; the "hello, world" example above uses every feature of JSX. To learn more about it, see [JSX in depth](./jsx-in-depth.html). Or see the transform in action in [our live JSX compiler](/react/jsx-compiler.html).
JSX is similar to HTML, but not exactly the same. See [JSX gotchas](./jsx-gotchas.html) for some key differences.
The easiest way to get started with JSX is to use the in-browser `JSXTransformer`. We strongly recommend that you don't use this in production. You can precompile your code using our command-line [react-tools](http://npmjs.org/package/react-tools) package.

View File

@@ -1,18 +1,17 @@
---
id: docs-syntax
title: JSX Syntax
description: Writing JavaScript with XML syntax.
id: jsx-in-depth
title: JSX in Depth
layout: docs
prev: common-questions.html
next: component-basics.html
permalink: jsx-in-depth.html
prev: displaying-data.html
next: jsx-gotchas.html
---
JSX is a JavaScript XML syntax transform recommended (but not required) for use
JSX is a JavaScript XML syntax transform recommended for use
with React.
## Why JSX?
First of all, **don't use JSX if you don't like it!**
## Why JSX?
React works out of the box without JSX. Simply construct your markup using the
functions on `React.DOM`. For example, here's how to construct a simple link:
@@ -23,21 +22,22 @@ var link = React.DOM.a({href: 'http://facebook.github.io/react'}, 'React');
We recommend using JSX for many reasons:
- It's easier to visualize the structure of the DOM.
- Designers are more comfortable making changes.
- It's familiar for those who have used MXML or XAML.
* It's easier to visualize the structure of the DOM.
* Designers are more comfortable making changes.
* It's familiar for those who have used MXML or XAML.
## The Transform
JSX transforms XML-like syntax into native JavaScript. It turns XML elements and
attributes into function calls and objects, respectively.
JSX transforms from an XML-like syntax into native JavaScript. XML elements and
attributes are transformed into function calls and objects, respectively.
```javascript
var Nav;
// Input (JSX):
var app = <Nav color="blue" />;
// Output (JS):
var app = Nav({color:'blue'}, null);
var app = Nav({color:"blue"});
```
Notice that in order to use `<Nav />`, the `Nav` variable must be in scope.
@@ -49,7 +49,7 @@ var Nav, Profile;
// Input (JSX):
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
// Output (JS):
var app = Nav({color:'blue'}, Profile(null, 'click'));
var app = Nav({color:"blue"}, Profile(null, "click"));
```
Use the [JSX Compiler](/react/jsx-compiler.html) to try out JSX and see how it
@@ -63,16 +63,17 @@ how to setup compilation.
> Details about the code transform are given here to increase understanding, but
> your code should not rely on these implementation details.
## React and JSX
React and JSX are independent technologies, but JSX was primarily built with
React in mind. The two valid uses of JSX are:
- To construct instances of React DOM components (`React.DOM.*`).
- To construct instances of composite components created with
* To construct instances of React DOM components (`React.DOM.*`).
* To construct instances of composite components created with
`React.createClass()`.
**React DOM Components**
### React DOM Components
To construct a `<div>` is to create a variable that refers to `React.DOM.div`.
@@ -81,7 +82,7 @@ var div = React.DOM.div;
var app = <div className="appClass">Hello, React!</div>;
```
**React Component Components**
### React Component Components
To construct an instance of a composite component, create a variable that
references the class.
@@ -114,7 +115,7 @@ var Nav;
// Input (JSX):
var tree = <Nav><span /></Nav>;
// Output (JS):
var tree = Nav(null, React.DOM.span(null, null));
var tree = Nav(null, React.DOM.span(null));
```
> Remember:
@@ -125,7 +126,7 @@ var tree = Nav(null, React.DOM.span(null, null));
## JavaScript Expressions
#### Attribute Expressions
### Attribute Expressions
To use a JavaScript expression as an attribute value, wrap the expression in a
pair of curly braces (`{}`) instead of quotes (`""`).
@@ -137,7 +138,7 @@ var person = <Person name={window.isLoggedIn ? window.name : ''} />;
var person = Person({name: window.isLoggedIn ? window.name : ''});
```
#### Child Expressions
### Child Expressions
Likewise, JavaScript expressions may be used to express children:
@@ -145,16 +146,24 @@ Likewise, JavaScript expressions may be used to express children:
// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// Output (JS):
var content = Container(null, window.isLoggedIn ? Nav(null, null) : Login(null, null));
var content = Container(null, window.isLoggedIn ? Nav(null) : Login(null));
```
### Comments
It's easy to add comments within your JSX; they're just JS expressions:
```javascript
var content = <Container>{/* this is a comment */}<Nav /></Container>;
```
## Tooling
Beyond the compilation step, JSX does not require any special tools.
- Many editors already include reasonable support for JSX (Vim, Emacs js2-mode).
- Linting provides accurate line numbers after compiling without sourcemaps.
- Elements use standard scoping so linters can find usage of out-of-scope
* Many editors already include reasonable support for JSX (Vim, Emacs js2-mode).
* Linting provides accurate line numbers after compiling without sourcemaps.
* Elements use standard scoping so linters can find usage of out-of-scope
components.
## Prior Work
@@ -163,6 +172,8 @@ JSX is similar to several other JavaScript embedded XML language
proposals/projects. Some of the features of JSX that distinguish it from similar
efforts include:
- JSX is a simple syntactic transform.
- JSX neither provides nor requires a runtime library.
- JSX does not alter or add to the semantics of JavaScript.
* JSX is a simple syntactic transform.
* JSX neither provides nor requires a runtime library.
* JSX does not alter or add to the semantics of JavaScript.
JSX is similar to HTML, but not exactly the same. See [JSX gotchas](./jsx-gotchas.html) for some key differences.

View File

@@ -1,14 +1,16 @@
---
id: docs-jsx-is-not-html
title: JSX is not HTML
description: Differences between JSX and HTML.
id: jsx-gotchas
title: JSX Gotchas
layout: docs
prev: api.html
permalink: jsx-gotchas.html
prev: jsx-in-depth.html
next: interactivity-and-dynamic-uis.html
---
JSX looks like HTML but there are some important differences you may run into.
## Whitespace removal
## Whitespace Removal
JSX doesn't follow the same whitespace elimination rules as HTML. JSX removes all whitespace between two curly braces expressions. If you want to have whitespace, simply add `{' '}`.
@@ -18,6 +20,7 @@ JSX doesn't follow the same whitespace elimination rules as HTML. JSX removes al
Follow [Issue #65](https://github.com/facebook/react/issues/65) for discussion on this behavior.
## HTML Entities
You can insert HTML entities within literal text in JSX:
@@ -58,27 +61,6 @@ As a last resort, you always have the ability to insert raw HTML.
<div dangerouslySetInnerHTML={{'{{'}}__html: 'First &middot; Second'}} />
```
## Comments
JSX supports both single-line and multi-line JavaScript comments within a tag declaration:
```javascript
<div // This is a single-line comment:
/*
And a multi-line
comment
*/
/>
```
As of React 0.3, there is no good way to insert comments within the children section. [Issue #82](https://github.com/facebook/react/issues/82) is tracking progress to enable the following:
```javascript
// Note: This is not implemented yet!
<div>
{/* This is a comment */}
</div>
```
## Custom HTML Attributes

View File

@@ -0,0 +1,89 @@
---
id: interactivity-and-dynamic-uis
title: Interactivity and Dynamic UIs
layout: docs
permalink: interactivity-and-dynamic-uis.html
prev: jsx-gotchas.html
next: multiple-components.html
---
You've already [learned how to display data](./displaying-data.html) with React. Now let's look at how to make our UIs interactive.
## A Simple Example
```javascript
/** @jsx React.DOM */
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'unlike';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
React.renderComponent(
<LikeButton />,
document.getElementById('example')
);
```
## Event Handling and Synthetic Events
With React you simply pass your event handler as a camelCased prop similar to how you'd do it in normal HTML. React ensures that all events behave identically in IE8 and above by implementing a synthetic event system. That is, React knows how to bubble and capture events according to the spec, and the events passed to your event handler are guaranteed to be consistent with [the W3C spec](http://www.w3.org/TR/DOM-Level-3-Events/), regardless of which browser you're using.
If you'd like to use React on a touch device (i.e. a phone or tablet), simply call `React.initializeTouchEvents(true);` to turn them on.
## Under the Hood: autoBind and Event Delegation
Under the hood React does a few things to keep your code performant and easy to understand.
**Autobinding:** When creating callbacks in JavaScript you usually need to explicitly bind a method to its instance such that the value of `this` is correct. With React, every method is automatically bound to its component instance. React caches the bound method such that it's extremely CPU and memory efficient. It's also less typing!
**Event delegation:** React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see [David Walsh's excellent blog post](http://davidwalsh.name/event-delegate).
## Components are Just State Machines
React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.
In React, you simply update a component's state, and then render a new UI based on this new state. React takes care of updating the DOM for you in the most efficient way.
## How State Works
A common way to inform React of a data change is by calling `setState(data, callback)`. This method merges `data` into `this.state` and re-renders the component. When the component finishes re-rendering, the optional `callback` is called. Most of the time you'll never need to provide a `callback` since React will take care of keeping your UI up-to-date for you.
## What Components Should Have State?
Most of your components should simply take some data from `props` and render it. However, sometimes you need to respond to user input, a server request or the passage of time. For this you use state.
**Try to keep as many of your components as possible stateless.** By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application.
A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via `props`. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
## What *Should* Go in State?
**State should contain data that a component's event handlers may change to trigger a UI update.** In real apps this data tends to be very small and JSON-serializable. When building a stateful component, think about the minimal possible representation of its state, and only store those properties in `this.state`. Inside of `render()` simply compute any other information you need based on this state. You'll find that thinking about and writing applications in this way tends to lead to the most correct application, since adding redundant or computed values to state means that you need to explicitly keep them in sync rather than rely on React computing them for you.
## What *Shouldn't* Go in State?
`this.state` should only contain the minimal amount of data needed to represent your UI's state. As such, it should not contain:
* **Computed data:** Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation within `render()`. For example, if you have an array of list items in state and you want to render the count as a string, simply render `this.state.listItems.length + ' list items'` in your `render()` method rather than storing it on state.
* **React components:** Build them in `render()` based on underlying props and state.
* **Duplicated data from props:** Try to use props as the source of truth where possible. Because props can change over time, it's appropriate to store props in state to be able to know its previous values.

View File

@@ -0,0 +1,151 @@
---
id: multiple-components
title: Multiple Components
layout: docs
permalink: multiple-components.html
prev: interactivity-and-dynamic-uis.html
next: reusable-components.html
---
So far, we've looked at how to write a single component to display data and handle user input. Next let's examine one of React's finest features: composability.
## Motivation: Separation of Concerns
By building modular components that reuse other components with well-defined interfaces, you get much of the same benefits that you get by using functions or classes. Specifically you can *separate the different concerns* of your app however you please simply by building new components. By building a custom component library for your application, you are expressing your UI in a way that best fits your domain.
## Composition Example
Let's create a simple Avatar component which shows a profile picture and username using the Facebook Graph API.
```javascript
/** @jsx React.DOM */
var Avatar = React.createClass({
render: function() {
return (
<div>
<ProfilePic username={this.props.username} />
<ProfileLink username={this.props.username} />
</div>
);
}
});
var ProfilePic = React.createClass({
render: function() {
return (
<img src={'http://graph.facebook.com/' + this.props.username + '/picture'} />
);
}
});
var ProfileLink = React.createClass({
render: function() {
return (
<a href={'http://www.facebook.com/' + this.props.username}>
{this.props.username}
</a>
);
}
});
React.renderComponent(
<Avatar username="pwh" />,
document.getElementById('example')
);
```
## Ownership
In the above example, instances of `Avatar` *own* instances of `ProfilePic` and `ProfileLink`. In React, **an owner is the component that sets the `props` of other components**. More formally, if a component `X` is created in component `Y`'s `render()` method, it is said that `X` is *owned by* `Y`. As discussed earlier, a component cannot mutate its `props` — they are always consistent with what its owner sets them to. This key property leads to UIs that are guaranteed to be consistent.
It's important to draw a distinciton between the owner-ownee relationship and the parent-child relationship. The owner-ownee relationship is specific to React, while the parent-child relationship is simply the one you know and love from the DOM. In the example above, `Avatar` owns the `div`, `ProfilePic` and `ProfileLink` instances, and `div` is the **parent** (but not owner) of the `ProfilePic` and `ProfileLink` instances.
## Children
When you create a React component instance, you can include additional React components or JavaScript expressions between the opening and closing tags like this:
```javascript
<Parent><Child /></Parent>
```
`Parent` can read its children by accessing the special `this.props.children` prop.
### Child Reconciliation
**Reconciliation is the process by which React updates the DOM with each new render pass.** In general, children are reconciled according to the order in which they are rendered. For example, suppose two render passes generate the following respective markup:
```html
// Render Pass 1
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// Render Pass 2
<Card>
<p>Paragraph 2</p>
</Card>
```
Intuitively, `<p>Paragraph 1</p>` was removed. Instead, React will reconcile the DOM by changing the text content of the first child and destroying the last child. React reconciles according to the *order* of the children.
### Stateful Children
For most components, this is not a big deal. However, for stateful components that maintain data in `this.state` across render passes, this can be very problematic.
In most cases, this can be sidestepped by hiding elements instead of destroying them:
```html
// Render Pass 1
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// Render Pass 2
<Card>
<p style={{'{{'}}display: 'none'}}>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
```
### Dynamic Children
The situation gets more complicated when the children are shuffled around (as in search results) or if new components are added onto the front of the list (as in streams). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a `key`:
```javascript
render: function() {
var results = this.props.results;
return (
<ol>
{this.results.map(function(result) {
return <li key={result.id}>{result.text}</li>;
})}
</ol>
);
}
```
When React reconciles the keyed children, it will ensure that any child with `key` will be reordered (instead of clobbered) or destroyed (instead of reused).
## Data Flow
In React, data flows from owner to owned component through `props` as discussed above. This is effectively one-way data binding: owners bind their owned component's props to some value the owner has computed based on its `props` or `state`. Since this process happens recursively, data changes are automatically reflected everywhere they are used.
## A Note on Performance
You may be thinking that it's expensive to react to changing data if there are a large number of nodes under an owner. The good news is that JavaScript is fast and `render()` methods tend to be quite simple, so in most applications this is extremely fast. Additionally, the bottleneck is almost always the DOM mutation and not JS execution and React will optimize this for you using batching and change detection.
However, sometimes you really want to have fine-grained control over your performance. In that case, simply override `shouldComponentUpdate()` to return false when you want React to skip processing of a subtree. See [the React reference docs](./reference.html) for more information.
> Note:
>
> If `shouldComponentUpdate()` returns false when data has actually changed, React can't keep your UI in sync. Be sure you know what you're doing while using it, and only use this function when you have a noticeable performance problem. Don't underestimate how fast JavaScript is relative to the DOM.

View File

@@ -0,0 +1,143 @@
---
id: reusable-components
title: Reusable Components
layout: docs
permalink: reusable-components.html
prev: multiple-components.html
next: forms.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, less bugs, and less bytes down the wire.
## Prop Validation
As your app grows it's helpful to ensure that your components are used correctly. We do this by allowing you to specify `propTypes`. `React.PropTypes` exports a range of validators that can be used to make sure the data you receive is valid. When an invalid value is provided for a prop, an error will be thrown. Here is an example documenting the different validators provided:
```javascript
React.createClass({
propTypes: {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: React.PropTypes.oneOf(['News','Photos']),
// You can also declare that a prop is an instance of a class. This uses
// JS's instanceof operator.
someClass: React.PropTypes.instanceOf(SomeClass),
// You can chain any of the above with isRequired to make sure an error is
// thrown if the prop isn't provide.
requiredFunc: React.PropTypes.func.isRequired
// You can also specify a custom validator.
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
throw new Error('Validation failed!')
}
}
},
/* ... */
});
```
## Default Prop Values
React lets you define default values for your `props` in a very declarative way:
```javascript
var ComponentWithDefaultProps = React.createClass({
getDefaultProps: function() {
return {
value: 'default value'
};
}
/* ... */
});
```
The result of `getDefaultProps()` will be cached and used to ensure that `this.props.value` will have a value if it was not specified by the parent component. This allows you to safely just use your props without having to write repetitive and fragile code to handle that yourself.
## Transferring Props: A Shortcut
A common type of React component is one that extends a basic HTML in a simple way. Often you'll want to copy any HTML attributes passed to your component to the underlying HTML element to save typing. React provides `transferPropsTo()` to do just this.
```javascript
/** @jsx React.DOM */
var CheckLink = React.createClass({
render: function() {
// transferPropsTo() will take any props pased to CheckLink
// and copy them to <a>
return this.transferPropsTo(<a>{'√ '}{this.props.children}</a>);
}
});
React.renderComponent(
<CheckLink href="javascript:alert('Hello, world!');">
Click here!
</CheckLink>,
document.getElementById('example')
);
```
## Mixins
Components are the best way to reuse code in React, but sometimes very different components may share some common functionality. These are sometimes called [cross-cutting concerns](http://en.wikipedia.org/wiki/Cross-cutting_concern). React provides `mixins` to solve this problem.
One common use case is a component wanting to update itself on a time interval. It's easy to use `setInterval()`, but it's important to cancel your interval when you don't need it anymore to save memory. React provides [lifecycle methods](./working-with-the-browser.html) that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide an easy `setInterval()` function that will automatically get cleaned up when your component is destroyed.
```javascript
/** @jsx React.DOM */
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
}
};
var TickTock = React.createClass({
mixins: [SetIntervalMixin], // Use the mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // Call a method on the mixin
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>
);
}
});
React.renderComponent(
<TickTock />,
document.getElementById('example')
);
```
A nice feature of mixins is that if a component is using multiple mixins and several mixins define the same lifecycle method (i.e. several mixins want to do some cleanup when the component is destroyed), all of the lifecycle methods are guaranteed to be called.

132
docs/docs/06-forms.md Normal file
View File

@@ -0,0 +1,132 @@
---
id: forms
title: Forms
layout: docs
permalink: forms.html
prev: reusable-components.html
next: working-with-the-browser.html
---
Form components such as `<input>`, `<textarea>`, and `<option>` differ from other native components because they can be mutated via user interactions. These components provide interfaces that make it easier to manage forms in response to user interactions.
## Interactive Props
Form components support a few props that are affected via user interactions:
* `value`, supported by `<input>` and `<textarea>` components.
* `checked`, supported by `<input>` components of type `checkbox` or `radio`.
* `selected`, supported by `<option>` components.
In HTML, the value of `<textarea>` is set via children. In React, you should use `value` instead.
Form components allow listening for changes by setting a callback to the `onChange` prop. The `onChange` prop works across browsers to fire in response to user interactions when:
* The `value` of `<input>` or `<textarea>` changes.
* The `checked` state of `<input>` changes.
* The `selected` state of `<option>` changes.
Like all DOM events, the `onChange` prop is supported on all native components and can be used to listen to bubbled change events.
## Controlled Components
An `<input>` with `value` set is a *controlled* component. In a controlled `<input>`, the value of the rendered element will always reflect the `value` prop. For example:
```javascript
render: function() {
return <input type="text" value="Hello!" />;
}
```
This will render an input that always has a value of `Hello!`. Any user input will have no effect on the rendered element because React has declared the value to be `Hello!`. If you wanted to update the value in response to user input, you could use the `onChange` event:
```javascript
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
var value = this.state.value;
return <input type="text" value={value} onChange={this.handleChange} />;
}
```
In this example, we are simply accepting the newest value provided by the user and updating the `value` prop of the `<input>` component. This pattern makes it easy to implement interfaces that respond to or validate user interactions. For example:
```javascript
handleChange: function(event) {
this.setState({value: event.target.value.substr(0, 140)});
}
```
This would accept user input but truncate the value to the first 140 characters.
## Uncontrolled Components
An `<input>` that does not supply a `value` (or sets it to `null`) is an *uncontrolled* component. In an uncontrolled `<input>`, the value of the rendered element will reflect the user's input. For example:
```javascript
render: function() {
return <input type="text" />;
}
```
This will render an input that starts off with an empty value. Any user input will be immediately reflected by the rendered element. If you wanted to listen to updates to the value, you could use the `onChange` event just like you can with controlled components.
If you want to initialize the component with a non-empty value, you can supply a `defaultValue` prop. For example:
```javascript
render: function() {
return <input type="text" defaultValue="Hello!" />;
}
```
This example will function much like the **Controlled Components** example above.
Likewise, `<input>` supports `defaultChecked` and `<option>` supports `defaultSelected`.
## Advanced Topics
### Why Controlled Components?
Using form components such as `<input>` in React presents a challenge that is absent when writing traditional form HTML. For example, in HTML:
```html
<input type="text" name="title" value="Untitled" />
```
This renders an input *initialized* with the value, `Untitled`. When the user updates the input, the node's value *property* will change. However, `node.getAttribute('value')` will still return the value used at initialization time, `Untitled`.
Unlike HTML, React components must represent the state of the view at any point in time and not only at initialization time. For example, in React:
```javascript
render: function() {
return <input type="text" name="title" value="Untitled" />;
}
```
Since this method describes the view at any point in time, the value of the text input should *always* be `Untitled`.
### Why Textarea Value?
In HTML, the value of `<textarea>` is usually set using its children:
```html
<!-- counterexample: DO NOT DO THIS! -->
<textarea name="description">This is the description.</textarea>
```
For HTML, this easily allows developers to supply multiline values. However, since React is JavaScript, we do not have string limitations and can use `\n` if we want newlines. In a world where we have `value` and `defaultValue`, it is ambiguous what role children play. For this reason, you should not use children when setting `<textarea>` values:
```javascript
<textarea name="description" value="This is a description." />
```
If you *do* decide to use children, they will behave like `defaultValue`.

View File

@@ -0,0 +1,132 @@
---
id: working-with-the-browser
title: Working With the Browser
layout: docs
permalink: working-with-the-browser.html
prev: forms.html
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
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.
Additionally, React implements a full synthetic event system such that all event objects are guaranteed to conform to the W3C spec despite browser quirks, and everything bubbles consistently and in a performant way cross-browser. You can even use some HTML5 events in IE8!
Most of the time you should stay within React's "faked browser" world since it's more performant and easier to reason about. However, sometimes you simply need to access the underlying API, perhaps to work with a third-party library like a jQuery plugin. React provides escape hatches for you to use the underlying DOM API directly.
## Refs and getDOMNode()
To interact with the browser, you'll need a reference to a DOM node. Every mounted React component has a `getDOMNode()` function which you can call to get a reference to it.
> Note:
>
> `getDOMNode()` only works on mounted components (that is, components that have been placed in the DOM). If you try to call this on a component that has not been mounted yet (like calling `getDOMNode()` in `render()` on a component that has yet to be created) an exception will be thrown.
In order to get a reference to a React component, you can either use `this` to get the current React component, or you can use refs to refer to a component you own. They work like this:
```javascript
/** @jsx React.DOM */
var MyComponent = React.createClass({
handleClick: function() {
// Explicitly focus the text input using the raw DOM API.
this.refs.myTextInput.getDOMNode().focus();
},
render: function() {
// The ref attribute adds a reference to the component to
// this.refs when the component is mounted.
return (
<div>
<input type="text" ref="myTextInput" />
<input
type="button"
value="Focus the text input"
onClick={this.handleClick}
/>
</div>
);
}
});
React.renderComponent(
<MyComponent />,
document.getElementById('example')
);
```
## More About Refs
To learn more about refs, including ways to use them effectively, see our [more about refs](./more-about-refs.html) documentation.
## Component Lifecycle
Components have three main parts of their lifecycle:
* **Mounting:** A component is being inserted into the DOM.
* **Updating:** A component is being re-rendered to determine if the DOM should be updated.
* **Unmounting:** A component is being removed from the DOM.
React provides lifecycle methods that you can specify to hook into this process. We provide **will** methods, which are called right before something happens, and **did** methods which are called right after something happens.
### Mounting
* `getInitialState(): object` is invoked before a component is mounted. Stateful components should implement this and return the initial state data.
* `componentWillMount()` is invoked immediately before mounting occurs.
* `componentDidMount(DOMElement rootNode)` is invoked immediately after mounting occurs. Initialization that requires DOM nodes should go here.
### Updating
* `componentWillReceiveProps(object nextProps)` is invoked when a mounted component receives new props. This method should be used to compare `this.props` and `nextProps` to perform state transitions using `this.setState()`.
* `shouldComponentUpdate(object nextProps, object nextState): boolean` is invoked when a component decides whether any changes warrant an update to the DOM. Implement this as an optimization to compare `this.props` with `nextProps` and `this.state` with `nextState` and return false if React should skip updating.
* `componentWillUpdate(object nextProps, object nextState)` is invoked immediately before updating occurs. You cannot call `this.setState()` here.
* `componentDidUpdate(object prevProps, object prevState, DOMElement rootNode)` is invoked immediately after updating occurs.
### Unmounting
* `componentWillUnmount()` is invoked immediately before a component is unmounted and destroyed. Cleanup should go here.
### Mounted Methods
_Mounted_ composite components also support the following methods:
* `getDOMNode(): DOMElement` can be invoked on any mounted component in order to obtain a reference to its rendered DOM node.
* `forceUpdate()` can be invoked on any mounted component when you know that some deeper aspect of the component's state has changed without using `this.setState()`.
> Note:
>
> The `DOMElement rootNode` argument of `componentDidMount()` and
> `componentDidUpdate()` is a convenience. The same node can be obtained by
> calling `this.getDOMNode()`.
## Browser Suppport and Polyfills
At Facebook, we support older browsers, including IE8. We've had polyfills in place for a long time to allow us to write forward-thinking JS. This means we don't have a bunch of hacks scattered throughout our codebase and we can still expect our code to "just work". For example, instead of seeing `+new Date()`, we can just write `Date.now()`. Since the open source React is the same as what we use internally, we've carried over this philosophy of using forward thinking JS.
In addition to that philosphy, we've also taken the stance that we, as authors of a JS library, should not be shipping polyfills as a part of our library. If every library did this, there's a good chance you'd be sending down the same polyfill multiple times, which could be a sizable chunk of dead code. If your product needs to support older browsers, chances are you're already using something like [es5-shim](https://github.com/kriskowal/es5-shim).
### Polyfills Needed to Support Older Browsers
* `Array.isArray`
* `Array.prototype.forEach`
* `Array.prototype.indexOf`
* `Function.prototype.bind`
* `Date.now`
* `Array.prototype.some` (also in `es5-shim.js`)
All of these can be polyfilled using `es5-shim.js` from [https://github.com/kriskowal/es5-shim](https://github.com/kriskowal/es5-shim).
* `console.*` - Only needed when not using the minified build. If you need to polyfill this, try [https://github.com/paulmillr/console-polyfill](https://github.com/paulmillr/console-polyfill).
* `Object.create` - Provided in `es5-sham.js` @ [https://github.com/kriskowal/es5-shim](https://github.com/kriskowal/es5-shim).

View File

@@ -0,0 +1,135 @@
---
id: more-about-refs
title: More About Refs
layout: docs
permalink: more-about-refs.html
prev: working-with-the-browser.html
next: tooling-integration.html
---
After returning the structure of your UI from the render method, you may find yourself wanting to "reach out" and invoke methods on component instances returned from render. Often, doing something like this isn't necessary for making data flow through your application, because the Reactive data flow always ensures that the most recent `props` are sent to each child that is output from `render()`. However there are a few cases, where it still might be necessary or beneficial.
Consider the case when you wish to tell an `<input />` element (that exists within your instances sub-hierarchy) to focus after you update its value to be the empty string, `''`.
```javascript
var App = React.createClass({
getInitialState: function() {
return {userInput: ''};
},
handleKeyUp: function(e) {
this.setState({userInput: e.target.value});
},
clearAndFocusInput: function() {
this.setState({userInput: ''}); // Clear the input
// We wish to focus the <input /> now!
},
render: function() {
return (
<div>
<div onClick={this.clearAndFocusInput}>
Click To Focus and Reset
</div>
<input
value={this.state.userInput}
onKeyUp={this.handleKeyUp}
/>
</div>
);
}
});
```
Notice how, in this example, we want to "tell" the input something - something that it cannot infer from it's props over time. In this case we want to "tell" it that it should now become focused. However, there are some challenges. What is returned from `render()`` is not your actual composition of "child" components, it is merely a *description* of the children at a particular instance in time - a snapshot, if you will.
> Note:
>
> Remember, what you return from `render()` is not your *actual* rendered children instances. What you return from `render()` is merely a *description* of the children instances in your component's sub-hierarchy at a particular moment in time.
This means that you should never "hold onto" something that you return from `render()` and then expect it to be anything meaningful.
```javascript
// counterexample: DO NOT DO THIS!
render: function() {
var myInput = <input />; // I'm going to try to call methods on this
this.rememberThisInput = myInput; // input at some point in the future! YAY!
return (
<div>
<div>...</div>
{myInput}
</div>
);
}
```
In this counterexample, the `<input />` is merely a *description* of an `<input />`. This description is used to create a *real* **backing instance** for the `<input />`.
So how do we talk to the *real* backing instance of the input?
## The ref Attribute
React supports a very special property that you can attach to any component that is output from `render()`. This special property allows you to refer to the corresponding **backing instance** of anything returned from `render()`. It is always guaranteed to be the proper instance, at any point in time.
It's as simple as:
**1.** Assign a `ref` attribute to anything returned from `render` such as:
```html
<input ref="myInput" />
```
**2.** In some other code (typically event handler code), access the **backing instance** via `this.refs` as in:
```javascript
this.refs.myInput
```
## Completing the Example
```javascript
var App = React.createClass({
getInitialState: function() {
return {userInput: ''};
},
handleKeyUp: function(e) {
this.setState({userInput: e.target.value});
},
clearAndFocusInput: function() {
this.setState({userInput: ''}); // Clear the input
this.refs.theInput.getDOMNode().focus(); // Boom! Focused!
},
render: function() {
return (
<div>
<div onClick={this.clearAndFocusInput}>
Click To Focus and Reset
</div>
<input
ref="theInput"
value={this.state.userInput}
onKeyUp={this.handleKeyUp}
/>
</div>
);
}
});
```
In this example, our render function returns a description of an `<input />` instance. But the true instance is accessed via `this.refs.theInput`. As long as a child component with `ref="theInput"` is returned from render, `this.refs.theInput` will access the the proper instance. This even works on higher level (non-DOM) components such as `<Typeahead ref="myTypeahead" />`.
## Summary
Refs are a great way to send a message to a particular child instance in a way that would be inconvenient to do via streaming Reactive `props` and `state`. They should, however, not be your go-to abstraction for flowing data through your application. By default, use the Reactive data flow and save `ref`s for use cases that are inherently non-reactive.
### Benefits:
- You can define any public method on your component classes (such as a reset method on a Typeahead) and call those public methods through refs (such as `this.refs.myTypeahead.reset()`).
- Performing DOM measurements almost always requires reaching out to a "native" component such as `<input />` and accessing its underlying DOM node via `this.refs.myInput.getDOMNode()`. Refs are one of the only practical ways of doing this reliably.
- Refs are automatically book-kept for you! If that child is destroyed, its ref is also destroyed for you. No worrying about memory here (unless you do something crazy to retain a reference yourself).
### Cautions:
- *Never* access refs inside of any component's render method - or while any component's render method is even running anywhere in the call stack.
- If you want to preserve Google Closure Compiler Crushing resilience, make sure to never access as a property what was specified as a string. This means you must access using `this.refs['myRefString']` if your ref was defined as `ref="myRefString"`.
- If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where `state` should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use `ref`s to "make things happen" - instead, the data flow will usually accomplish your goal.

View File

@@ -0,0 +1,44 @@
---
id: tooling-integration
title: Tooling integration
layout: docs
permalink: tooling-integration.html
prev: more-about-refs.html
next: reference.html
---
Every project uses a different system for building and deploying JavaScript. We've tried to make React as environment-agnostic as possible.
## CDN-hosted React
We provide CDN-hosted versions of React [on our download page](/react/downloads.html). These prebuilt files use the UMD module format. Dropping them in with a simple `<script>` tag will inject a `React` global into your environment. It should also work out-of-the-box in CommonJS and AMD environments.
## Using master
We have instructions for building from `master` [in our GitHub repository](https://github.com/facebook/react). We build a tree of CommonJS modules under `build/modules` which you can drop into any environment or packaging tool that supports CommonJS.
## In-browser JSX Transform
If you like using JSX, we provide an in-browser JSX transformer for development [on our download page](/react/downloads.html). Simply include a `<script type="text/jsx">` tag to engage the JSX transformer. Be sure to include the `/** @jsx React.DOM */` comment as well, otherwise the transformer will not run the transforms.
> Note:
>
> The in-browser JSX transformer is fairly large and results in extraneous computation client-side that can be avoided. Do not use it in production — see the next section.
## Productionizing: Precompiled JSX
If you have [npm](http://npmjs.org/), you can simply run `npm install -g react-tools` to install our command-line `jsx` tool. This tool will translate files that use JSX syntax to plain JavaScript files that can run directly in the browser. It will also watch directories for you and automatically transform files when they are changed; for example: `jsx --watch src/ build/`. Run `jsx --help` for more information on how to use this tool.
## Helpful Open-Source Projects
The open-source community has built tools that integrate JSX with several build systems.
* [reactify](https://github.com/andreypopp/reactify) - use JSX with [browserify](http://browserify.org/).
* [grunt-react](https://github.com/ericclemmons/grunt-react) - [grunt](http://gruntjs.com/) task for JSX
* [require-jsx](https://github.com/seiffert/require-jsx) - use JSX with [require.js](http://requirejs.org/)
* [reactapp](https://github.com/jordwalke/reactapp) - a sample project to get up-and-running with React quickly

209
docs/docs/09-reference.md Normal file
View File

@@ -0,0 +1,209 @@
---
id: reference
title: Reference
layout: docs
permalink: reference.html
prev: tooling-integration.html
---
## Examples
### Production Apps
* All of [Instagram.com](http://instagram.com/) is built on React.
* Many components on [Facebook.com](http://www.facebook.com/), including the commenting interface, ads creation flows, and page insights.
* [Khan Academy](http://khanacademy.org/) is using React for its question editor.
### Sample Code
* We've included [a step-by-step comment box tutorial](./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/).
* [reactapp](https://github.com/jordwalke/reactapp) is a simple app template to get you up-and-running quickly with React.
* [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.
## API
### React
`React` is the entry point to the React framework. If you're using one of the prebuilt packages it's available as a global; if you're using CommonJS modules you can `require()` it.
#### React.DOM
`React.DOM` provides all of the standard HTML tags needed to build a React app. You generally don't use it directly; instead, just include it as part of the `/** @jsx React.DOM */` docblock.
#### React.initializeTouchEvents
```javascript
initializeTouchEvents(boolean shouldUseTouch)
```
Configure React's event system to handle touch events on mobile devices.
#### React.createClass
```javascript
function createClass(object specification)
```
Creates a component given a specification. A component implements a `render` method which returns **one single** child. That child may have an arbitrarily deep child structure. One thing that makes components different than a standard prototypal classes is that you don't need to call new on them. They are convenience wrappers that construct backing instances (via new) for you.
#### React.renderComponent
```javascript
ReactComponent renderComponent(ReactComponent container, DOMElement container)
```
Renders a React component into the DOM in the supplied `container`.
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.
#### React.unmountAndReleaseReactRootNode
```javascript
unmountAndReleaseReactRootNode(DOMElement container)
```
Remove a mounted React component from the DOM and clean up its event handlers and state.
#### React.renderComponentToString
```javascript
renderComponentToString(ReactComponent component, function callback)
```
Render a component to its initial HTML. This should only be used on the server. React will call `callback` with an HTML string when the markup is ready. You can use this method to create static site generators, or you can generate HTML on the server and send it down to have a very fast initial page load. If you call `React.renderComponent()` on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
### AbstractEvent
Your event handlers will be passed instances of `AbstractEvent`, a cross-browser wrapper around the browser's native event. It has the same interface as the browser's native event (such as `stopPropagation()` and `preventDefault()`) except they work exactly the same across all browsers.
If you find that you need the underlying browser event for some reason, simply use the `nativeEvent` attribute to get it.
### ReactComponent
Component classses created by `createClass()` return instances of `ReactComponent` when called. Most of the time when you're using React you're either creating or consuming `ReactComponent`s.
#### getDOMNode
```javascript
DOMElement getDOMNode()
```
If this component has been mounted into the DOM, this returns the corresponding native browser DOM element. This method is useful for reading values out of the DOM, such as form field values and performing DOM measurements.
#### setProps
```javascript
setProps(object nextProps)
```
When you're integrating with an external JavaScript application you may want to signal a change to a React component rendered with `renderComponent()`. Simply call `setProps()` to change its properties and trigger a re-render.
> Note:
>
> This method can only be called on a root-level component. That is, it's only available on the component passed directly to `renderComponent()` and none of its children. If you're inclined to use `setProps()` on a child component, instead take advantage of reactive updates and pass the new prop to the child component when it's created in `render()`.
#### replaceProps
```javascript
replaceProps(object nextProps)
```
Like `setProps()` but deletes any pre-existing props that are not in nextProps.
#### 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. This is usually used to pass down properties to the returned root component. `targetComponent`, now updated with some new props is returned as a convenience.
#### setState
```javascript
setState(object nextState[, function callback])
```
Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks. In addition, you can supply an optional callback function that is executed once `setState` is completed.
> Note:
>
> *NEVER* mutate `this.state` directly. As calling `setState()` afterwards may replace the mutation you made. Treat `this.state` as if it were immutable.
> Note:
>
> `setState()` does not immediately mutate `this.state` but creates a pending state transition. Accessing `this.state` after calling this method can potentially return the existing value.
> Note:
>
> There is no guarantee of synchronous operation of calls to `setState` and calls may be batched for performance gains.
#### replaceState
```javascript
replaceState(object nextState[, function callback])
```
Like `setState()` but deletes any pre-existing state keys that are not in nextState.
#### forceUpdate()
```javascript
forceUpdate([function callback])
```
If your `render()` method reads from something other than `this.props` or `this.state` you'll need to tell React when it needs to re-run `render()`. Use `forceUpdate()` to cause React to automatically re-render. This will cause `render()` to be called on the component and all of its children but React will only update the DOM if the markup changes.
Normally you should try to avoid all uses of `forceUpdate()` and only read from `this.props` and `this.state` in `render()`. This makes your application much simpler and more efficient.
> Note:
>
> There is no guarantee of synchronous operation of calls to `forceUpdate` and calls may be batched for performance gains.
#### Lifecycle methods
```javascript
object getInitialState()
componentWillMount()
componentDidMount(DOMElement domNode)
componentWillReceiveProps(object nextProps)
boolean shouldComponentUpdate(object nextProps, object nextState)
componentWillUpdate(object nextProps, object nextState)
ReactComponent render()
componentDidUpdate(object prevProps, object prevState, DOMElement domNode)
componentWillUnmount()
```
See the [working with the browser](./working-with-the-browser.html) documentation for more details on these lifecycle methods.
## DOM Differences
React has implemented a browser-independent events and DOM system for performance and cross-browser compatibility reasons. We took the opportunity to clean up a few rough edges in browser DOM implementations.
* All events (including submit) bubble correctly per the W3C spec
* All event objects conform to the W3C spec
* All DOM properties and attributes (including event handlers) should be camelCased to be consistent with standard JavaScript style. We intentionally break with the spec here, since the spec is inconsistent.
* The `style` attribute accepts a JavaScript object with camelCased properties rather than a CSS string. This is consistent with the DOM `style` JavaScript property, is more efficient, and prevents XSS security holes.
* `onChange` behaves as you would expect it to: whenever a form field is changed this event is fired rather than inconsistently on blur. We intentionally break from existing browser behavior because `onChange` is a misnomer for its behavior and React relies on this event to react to user input in real time.

96
docs/docs/OUTLINE.md Normal file
View File

@@ -0,0 +1,96 @@
---
id: OUTLINE
title: Goals of the documentation
layout: docs
prev: 09.1-tutorial.html
---
- Flow of docs should mimic progression of questions a new user would ask
- High information density -- assume the reader is adept at JS
- Talk about best practices
- JSFiddles for all code samples
- Provide background for some of the design decisions
- Less words less words less words!
## Outline
Motivation / Why React?
- Declarative (simple)
- Components (separation of concerns)
- Give it 5 minutes
Displaying data
- Hello world example
- Reactive updates
- Components are just functions
- JSX syntax (link to separate doc?)
- JSX gotchas
Interactivity and dynamic UIs
- Click handler example
- Event handlers / synthetic events (link to w3c docs)
- Under the hood: autoBind and event delegation (IE8 notes)
- React is a state machine
- How state works
- What components should have state?
- What should go in state?
- What shouldn't go in state?
Scaling up: using multiple components
- Motivation: separate concerns
- Composition example
- Ownership (and owner vs. parent)
- Children
- Data flow (one-way data binding)
- A note on performance
Building effective reusable components
- You should build a reusable component library (CSS, testing etc)
- Prop validation
- Transferring props: a shortcut
- Mixins
- Testing
Forms
Working with the browser
- The mock DOM
- Refs / getDOMNode()
- More about refs
- Component lifecycle
- Browser support and polyfills
Working with your environment
- CDN-hosted React
- Using master
- In-browser JSX transform
- Productionizing: precompiled JSX
- Helpful open-source projects
Integrating with other UI libraries
- Using jQuery plugins
- Letting jQuery manage React components
- Using with Backbone.View
- CoffeeScript
- Moving from Handlebars to React: an example
Server / static rendering
- Motivation
- Simple example
- How does it work? (No DOM)
- Rendr + React
Big ideas
- Animation
- Bootstrap bindings (responsive grids)
- Reactive CSS
- Web workers
- Native views
Case studies
- Comment box tutorial from scratch
- From HTML mock to application: React one-hour email
- Jordan's LikeToggler example
Reference
- API
- DOM differences

View File

@@ -1,119 +0,0 @@
---
id: docs-advanced-components
title: Advanced Components
description: How to build advanced composite components.
layout: docs
prev: event-handling.html
next: mixins.html
---
Composite components extend a `ReactCompositeComponent` base class that provides
a very powerful API that makes React flexible and able to easily work with other
libraries and frameworks.
## Lifecycle Methods
Composite components can optionally implement lifecycle methods that are invoked
at various stages in the [component lifecycle](component-lifecycle.html) that
each have unique guarantees.
### Mounting
- `getInitialState(): object` is invoked before a component is mounted.
Stateful components should implement this and return the initial state data.
- `componentWillMount()` is invoked immediately before mounting occurs.
- `componentDidMount(DOMElement rootNode)` is invoked immediately after
mounting occurs. Initialization that requires DOM nodes should go here.
### Updating
- `componentWillReceiveProps(object nextProps)` is invoked when a mounted
component receives new props. This method should be used to compare
`this.props` and `nextProps` to perform state transitions using
`this.setState()`.
- `shouldComponentUpdate(object nextProps, object nextState): boolean` is
invoked when a component decides whether any changes warrant an update to the
DOM. Implement this as an optimization to compare `this.props` with
`nextProps` and `this.state` with `nextState` and return false if React
should skip updating.
- `componentWillUpdate(object nextProps, object nextState)` is invoked
immediately before updating occurs. You cannot call `this.setState()` here.
- `componentDidUpdate(object prevProps, object prevState, DOMElement rootNode)`
is invoked immediately after updating occurs.
### Unmounting
- `componentWillUnmount()` is invoked immediately before a component is
unmounted and destroyed. Cleanup should go here.
## Mounted Methods
_Mounted_ composite components also support the following methods:
- `getDOMNode(): DOMElement` can be invoked on any mounted component in order
to obtain a reference to its rendered DOM node.
- `forceUpdate()` can be invoked on any mounted component when you know that
some deeper aspect of the component's state has changed without using
`this.setState()`.
> Note:
>
> The `DOMElement rootNode` argument of `componentDidMount()` and
> `componentDidUpdate()` is a convenience. The same node can be obtained by
> calling `this.getDOMNode()`.
## Component Refs
A common use case of event callbacks or the lifecycle methods is to operate on a
component returned by `render()`. For example, consider a search component that
should auto-focus the input once mounted:
```javascript
var SearchForm = React.createClass({
render: function() {
return (
<form action={this.props.action}>
<input type="search" placeholder="Search..." />
</form>
);
},
componentDidMount: function(rootNode) {
var searchInput = rootNode.firstChild;
searchInput.focus();
}
});
```
Although this implementation works, it is fragile because `componentDidMount()`
now relies on `render()` returning a particular DOM structure.
React provides a better way for composite components to reference components
that it constructs in its `render()` method through the use of refs. A component
can assign a `ref` to any component it constructs. This will create a reference
to those components on `this.refs`. For example:
```javascript{5,10}
var SearchForm = React.createClass({
render: function() {
return (
<form action={this.props.action}>
<input type="search" placeholder="Search..." ref="searchInput" />
</form>
);
},
componentDidMount: function(rootNode) {
var searchInput = this.refs.searchInput.getDOMNode();
searchInput.focus();
}
});
```
In this example, `this.refs.searchInput` will reference the `<input>` component
and is available in most lifecycle methods and event callbacks. We obtain a
reference to the `<input>`'s DOM node using `getDOMNode()`.
> Note:
>
> If you want to preserve compatibility with Google Closure Compiler's
> property crushing in `ADVANCED_OPTIMIZATIONS` mode, make sure to use string
> literals with `this.refs`.

View File

@@ -1,151 +0,0 @@
---
id: docs-api
title: React API
layout: docs
prev: mixins.html
next: jsx-is-not-html.html
---
## React
`React` is the entry point to the React framework. If you're using one of the prebuilt packages it's available as a global; if you're using CommonJS modules you can `require()` it.
#### React.DOM
`React.DOM` provides all of the standard HTML tags needed to build a React app. You generally don't use it directly; instead, just include it as part of the `/** @jsx React.DOM */` docblock.
#### React.initializeTouchEvents
```javascript
initializeTouchEvents(boolean shouldUseTouch)
```
Configure React's event system to handle touch events on mobile devices.
#### React.autoBind
```javascript
function autoBind(function method)
```
Marks the provided function to be automatically bound to each React component instance created. This allows React components to define automatically bound methods and ensure that when called they will always reference their current instance.
Example:
```javascript
React.createClass({
click: React.autoBind(function(evt) {
this.setState({jumping: true});
}),
render: function() {
// Look: no bind!
return <a onClick={this.click}>Jump</a>;
}
});
```
#### React.createClass
```javascript
function createClass(object specification)
```
Creates a component given a specification. A component implements a `render` method which returns **one single** child. That child may have an arbitrarily deep child structure. One thing that makes components different than a standard prototypal classes is that you don't need to call new on them. They are convenience wrappers that construct backing instances (via new) for you.
#### React.renderComponent
```javascript
ReactComponent renderComponent(ReactComponent container, DOMElement mountPoint)
```
Renders a React component into the DOM in the supplied `container`.
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.
## AbstractEvent
Your event handlers will be passed instances of `AbstractEvent`, a cross-browser wrapper around the browser's native event. It has the same interface as the browser's native event (such as `stopPropagation()` and `preventDefault()`) except they work exactly the same across all browsers.
If you find that you need the underlying browser event for some reason, simply use the `nativeEvent` attribute to get it.
## ReactComponent
Component classses created by `createClass()` return instances of `ReactComponent` when called. Most of the time when you're using React you're either creating or consuming `ReactComponent`s.
#### getDOMNode
```javascript
DOMElement getDOMNode()
```
If this component has been mounted into the DOM, this returns the corresponding native browser DOM element. This method is useful for reading values out of the DOM, such as form field values and performing DOM measurements.
#### setProps
```javascript
setProps(object nextProps)
```
When you're integrating with an external JavaScript application you may want to signal a change to a React component rendered with `renderComponent()`. Simply call `setProps()` to change its properties and trigger a re-render.
**Note:** This method can only be called on a root-level component. That is, it's only available on the component passed directly to `renderComponent()` and none of its children. If you're inclined to use `setProps()` on a child component, instead take advantage of reactive updates and pass the new prop to the child component when it's created in `render()`.
#### replaceProps
```javascript
replaceProps(object nextProps)
```
Like `setProps()` but deletes any pre-existing props that are not in nextProps.
#### 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. This is usually used to pass down properties to the returned root component. `targetComponent`, now updated with some new props is returned as a convenience.
#### setState
```javascript
setState(object nextState)
```
Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks.
**Note:** *NEVER* mutate `this.state` directly. As calling `setState()` afterwards may replace the mutation you made. Treat `this.state` as if it were immutable.
**Note:** `setState()` does not immediately mutate `this.state` but creates a pending state transition. Accessing `this.state` after calling this method can potentially return the existing value.
#### replaceState
```javascript
replaceState(object nextState)
```
Like `setState()` but deletes any pre-existing state keys that are not in nextState.
#### forceUpdate()
```javascript
forceUpdate()
```
If your `render()` method reads from something other than `this.props` or `this.state` you'll need to tell React when it needs to re-run `render()`. Use `forceUpdate()` to cause React to automatically re-render. This will cause `render()` to be called on the component and all of its children but React will only update the DOM if the markup changes.
Normally you should try to avoid all uses of `forceUpdate()` and only read from `this.props` and `this.state` in `render()`. This makes your application much simpler and more efficient.
```javascript
object getInitialState()
componentWillMount()
componentDidMount(DOMElement domNode)
componentWillReceiveProps(object nextProps)
boolean shouldComponentUpdate(object nextProps, object nextState)
componentWillUpdate(object nextProps, object nextState)
ReactComponent render()
componentDidUpdate(object prevProps, object prevState, DOMElement domNode)
componentWillUnmount()
```
See the [advanced components](advanced-components.html) documentation for more details on these lifecycle methods.

View File

@@ -1,28 +0,0 @@
---
id: docs-common-questions
title: Common Questions
layout: docs
prev: tutorial.html
next: syntax.html
---
### What browsers does React support?
React supports the latest two Chrome, Firefox, Safari, and Internet Explorer versions. React can work with Internet Explorer 8 with polyfills.
### How do I get React to support Internet Explorer 8?
React requires ES5 JavaScript shims to run in Internet Explorer 8. Include the [ES5 Shims](https://github.com/kriskowal/es5-shim) to implement these shims.
### Who uses React?
The [Instagram](http://instagram.com/) website is built entirely in React. The [Facebook](https://www.facebook.com/) website is also increasingly using React, including the common commenting plugin across the site.
### I don't get it. React is confusing!
[This blog post by a member of the React team](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood) talks about some of the reasons
why React is designed the way that it is.
### Can I integrate with other JavaScript libraries?
Absolutely! In fact, we encourage it! See [our GitHub repo](http://github.com/facebook/react/) for a [TodoMVC example using Backbone](https://github.com/facebook/react/tree/master/examples/todomvc-backbone) and a [jQuery + Bootstrap modal demo](https://github.com/facebook/react/tree/master/examples/jquery-bootstrap).

View File

@@ -1,73 +0,0 @@
---
id: docs-component-basics
title: Component Basics
description: What are components?
layout: docs
next: component-data.html
prev: syntax.html
---
_Components_ are the basic units of composition in React. Components encapsulate
the logic necessary to take input parameters and render markup. Components can
be rendered into an existing DOM element on the page by using
`React.renderComponent`:
```javascript
// Replaces everything in `document.body` with <div>Hello, world!</div>;
React.renderComponent(<div>Hello, world!</div>, document.body);
```
Keep in mind that `<div>` is **not** a DOM element! Keep reading...
## Types of Components
There are two types of components:
- **Composite Components**
- **DOM Components**
### Composite Components <small>such as `TodoApp` and `Typeahead`.</small>
The majority of your React code will be implementing composite components.
Composite components are higher-level components with custom rendering logic
that may compose other composite components or DOM components.
```javascript
/** @jsx React.DOM */
var LinkButton = React.createClass({
render: function() {
return <a className="btn" />;
}
});
var myButton = <LinkButton />;
```
This example defines a `LinkButton` component class using `React.createClass()`,
and its `render()` method composes the `<a>` DOM component.
### DOM Components <small>such as `div` and `span`.</small>
DOM components are the set of classes that correspond to browser DOM elements.
They are defined in `React.DOM` and can be brought "into scope" by setting
`@jsx React.DOM` in the docblock. See [JSX Syntax](syntax.html) for more
details.
Although `React.DOM` components look like browser DOM elements, they differ in a
few ways:
- All property names, including event handlers, are camelCased.
- JavaScript identifiers should be used, namely `className` and `htmlFor`.
- The `style` prop expects an object instead of a string. The object should map
camelCased style properties to values, e.g. `{backgroundColor: '#fff'}`.
Here is an example of a React link styled as a button with a click handler:
```javascript
/** @jsx React.DOM */
var handleClick = function() {alert('Clicked!');};
var inlineStyle = {textDecoration: 'none'};
var myLink = <a className="btn" onClick={handleClick} style={inlineStyle} />;
```

View File

@@ -1,145 +0,0 @@
---
id: docs-component-data
title: Component Data
description: How is data passed into a component?
layout: docs
prev: component-basics.html
next: component-lifecycle.html
---
## Props
Components use data to determine what should be rendered. For example:
```javascript
var LikeLink = React.createClass({
render: function() {
var text = this.props.liked ? 'Liked' : 'Like';
return <a>{text}</a>;
}
});
var myLikeLink = <LikeLink liked={false} />;
```
In this example, `LikeLink` takes `liked` as boolean data. This type of data
that is passed in is called a "prop". Examples of props on DOM components
include `className` and `onClick`.
Whenever a component's props change, its `render()` function will be
re-evaluated and the DOM will be updated. React will ensure that the DOM is
always kept up-to-date.
## State
Let's build a small `LikeApp` application that makes use of the `<LikeLink>`
component from above. It should start off unliked and we should be able to like
it by clicking the link:
```javascript
var LikeApp = React.createClass({
render: function() {
var isClicked = false;
return <LikeLink liked={isClicked} onClick={this.handleClick.bind(this)} />;
},
handleClick: function() {
// Somehow update `isClicked`.
}
});
```
This renders a `<LikeLink>` with a click listener. However, it is not clear how
`handleClick` should update `isClicked` to true. `LikeApp` needs a way to store
**state** about whether or not it has been clicked.
### State vs. Props
State is data that is managed _internally_ by a composite component. Like props,
the `render()` function will be re-evaluated whenever state changes. Props and
state differ in that:
- Props are passed in from the creator.
- State is private to and managed by the component.
### Managing State
Let's update our `LikeApp` component using state:
```javascript{2-4,6,10}
var LikeApp = React.createClass({
getInitialState: function() {
return {isClicked: false};
},
render: function() {
var isClicked = this.state.isClicked;
return <LikeLink liked={isClicked} onClick={this.handleClick.bind(this)} />;
},
handleClick: function() {
this.setState({isClicked: true});
}
});
```
There's a lot going on here, so let's work our way from top to bottom:
- `getInitialState()` describes what state data looks like when the component
is created.
- In `render()`, state data can be accessed via `this.state`.
- When the link is clicked, we update state using `setState()`.
Now when we click the link, the `<LikeLink>` will get updated, right? Wrong.
## Transferring Props
If you have been following carefully, you may have noticed that although we pass
a click handler into `<LikeLink>` as a prop, `LikeLink` does not do anything
with `this.props.onClick`! Let's fix that.
```javascript{4}
var LikeLink = React.createClass({
render: function() {
var text = this.props.liked ? 'Liked' : 'Like';
return <a onClick={this.props.onClick}>{text}</a>;
}
});
```
Although this works, realize that this would quickly become tedious if we wanted
to also transfer `href`, `title`, `target`, and other events from `this` to the
rendered `<a>`. React provides a convenience method, `transferPropsTo()`, for
transferring props:
```javascript{4}
var LikeLink = React.createClass({
render: function() {
var text = this.props.liked ? 'Liked' : 'Like';
return this.transferPropsTo(<a>{text}</a>);
}
});
```
This will transfer all props from `this` to the specified component (including
`onClick`).
## Summary
Now we are done. `LikeApp` renders an unliked link which, when clicked, will:
1. Update the internal state of `LikeApp`.
2. Change the props passed into `LikeLink`.
3. Change the return value of `render()`.
4. Trigger an update to the DOM.
It's worth noting that React will handle new return values of `render()` by
making the minimal set of mutations necessary to bring the DOM up-to-date. In
this case, only the `textContent` of the rendered link will be mutated.
In summary:
- Props are passed in whereas state is managed internally by a component.
- Never mutate `this.props` or `this.state`. You should pass props into other
components and mutate state using `setState()`.
- State is private. Never read `state` or call `setState()` on
anything but `this`.
- Whenever props or state changes, `render()` will be re-evaluated and the DOM
updated. Also, `render()` should not depend on anything besides `this.props`
and `this.state`.

View File

@@ -1,85 +0,0 @@
---
id: docs-component-lifecycle
title: Component Lifecycle
description: What happens when I render a React component?
layout: docs
prev: component-data.html
next: event-handling.html
---
## Mounting
[We have previously seen](component-basics.html) how to render components into
existing DOM elements on the page:
```javascript
React.renderComponent(<div>Hello, world!</div>, document.body);
```
In this one simple line, we have accomplished the following:
- A `<div>` (defined by `React.DOM.div`) component is instantiated.
- The component is **mounted** into `document.body`.
**Mounting** is the process of initializing a React component by creating its
DOM nodes and inserting them into a supplied container node.
At this point, the entire page consists of a single `<div>` with "Hello,
world!".
## Updating
Let's add a second call to `React.renderComponent()` after three
seconds:
```javascript{2-4}
React.renderComponent(<div>Hello, world!</div>, document.body);
setTimeout(function() {
React.renderComponent(<div>Goodbye, world.</div>, document.body);
}, 3000);
```
The second call to `React.renderComponent()` will trigger the following:
- The `<div>` component will check the new props to see if anything changed.
- The set of changes are used to **update** the DOM node as necessary.
**Updating** is the process of mutating the rendered DOM nodes and occurs
whenever either props or state has changed. This ensures that the rendered DOM
is consistent with the data.
## Unmounting
Let's add one final call to `React.renderComponent()` after another three
seconds:
```javascript{5-7}
React.renderComponent(<div>Hello, world!</div>, document.body);
setTimeout(function() {
React.renderComponent(<div>Goodbye, world.</div>, document.body);
}, 3000);
setTimeout(function() {
React.renderComponent(<img src="/images/fin.png" />, document.body);
}, 6000);
```
The third call to `React.renderComponent()` will trigger the following:
- An `<img>` (defined by `React.DOM.img`) component is instantiated.
- React will compare the `<div>` component with the `<img>` component.
- Since the component class is different, the `<div>` component will be
**unmounted**.
- The `<img>` component will then be mounted into `document.body`.
**Unmounting** is the process of releasing resources that have been allocated by
a component. This allows user interfaces built with React to live long without
memory leaks.
Components can also be unmounted using
`React.unmountAndReleaseReactRootNode()`:
```javascript
React.unmountAndReleaseReactRootNode(document.body);
```
This will unmount any components mounted immediately within `document.body`.

View File

@@ -1,224 +0,0 @@
---
id: docs-event-handling
title: Event Handling
description: How do events work with React components?
layout: docs
prev: component-lifecycle.html
next: advanced-components.html
---
Events in React work the way they do with HTML, except the event names are
camelCased.
```javascript
var Clicker = React.createClass({
render: function() {
return <span onClick={this.handleClick}>Click me!</span>;
},
handleClick: function(event) {
alert('You clicked me!');
}
});
```
When `<Clicker>` is clicked, the `handleClick()` function will get fired. Under
the hood, React uses top-level event delegation to achieve high performance.
## Automatically Binding Callbacks
Just like any callback in JavaScript, if you want to refer to the component as
`this` from the callback, you need to bind the callback to the component:
```javascript{3}
var Clicker = React.createClass({
render: function() {
var handleClick = this.handleClick.bind(this);
return <span onClick={handleClick}>Click me!</span>;
},
handleClick: function(event) {
alert(this.ALERT_MESSAGE);
},
ALERT_MESSAGE: 'You clicked me!'
});
```
React provides a convenient and _efficient_ way to bind methods using
`React.autoBind()`:
```javascript{3,5-7}
var Clicker = React.createClass({
render: function() {
return <span onClick={this.handleClick}>Click me!</span>;
},
handleClick: React.autoBind(function(event) {
alert(this.ALERT_MESSAGE);
}),
ALERT_MESSAGE: 'You clicked me!'
});
```
> Note:
>
> Binding a function allocates memory to create a new bound function. Since
> `render()` may be invoked many times, it is a bad place to bind functions.
> `React.autoBind()` sidesteps this issue by only binding once at instantiation
> time.
## DOM Events
React uses [top-level event delegation](http://davidwalsh.name/event-delegate)
to achieve high performance when implementing DOM events. For each type of DOM
event, React adds a single top-level listener and determines which event
handlers to execute by simulating event capturing and bubbling.
DOM event handlers are called with a normalized `AbstractEvent` object that has
cross-browser compatible implementations of `stopPropagation` and
`preventDefault()`. If you need access to the raw browser event, you can use the
`nativeEvent` property.
> Note:
>
> The `AbstractEvent` object is JSON serializable so that React applications can
> be executed inside web workers.
### Touch Events
If you want to use touch events, you must configure React's event system to
initialize them:
```javascript
// Invoke before calling `React.renderComponent()`.
React.initializeTouchEvents(true);
```
## Custom Events
Notice that event listeners are attached by simply passing them into components
as props. For DOM components, events are handled using top-level event
delegation. For composite components, event handling is up to the component's
implementation.
Here is an example of a toggle link that fires a custom `onToggle` event:
```javascript
var ToggleLink = React.createClass({
getInitialState: function() {
return {isEnabled: false};
},
render: function() {
return <a onClick={this.handleClick}>Toggle</a>;
},
handleClick: React.autoBind(function() {
var willEnable = !this.state.isEnabled;
if (this.props.onToggle) {
this.props.onToggle(willEnable)
}
this.setState({isEnabled: willEnable});
})
});
var handleToggle = function(enabled) {
alert(enabled ? 'Enabled.' : 'Disabled.');
};
var myToggleLink = <ToggleLink onToggle={handleToggle} />;
```
### Common Patterns
With React your event handlers should be quite small. Large event handlers may
be symptomatic of code that should be moved into helpers or into `render()`.
Here are some common usage patterns for event handlers.
#### Updating State
The most common thing to do in response to a user action is to call
`this.setState()` to update the component's state, which will in turn trigger
an update to the rendered component.
#### Server Requests
Many event handlers will issue a server request to read or write some data in
response to an event. The response handler for the request will often call
`this.setState()`.
#### Invoke a Callback
Your component will often be a small, reusable building block that does not know
how to respond to a user action. In these situations, we delegate the
responsibility to the owner by exposing a handler on `this.props`. This is what
the `ToggleLink` example above is doing.
#### Inter-component Communication
A common scenario involves communicating to **Component A** that a user action
has occurred on **Component B**. To solve this problem, a common parent to
both components should listen for the event on **Component B**, update its
internal state, and pass that data into **Component A**.
For example, say we have two components: **Clicker**, a component that fires an
`onCountChange` custom event, and **ClickCountLabel**, a component that displays
the number of clicks that have happened:
```javascript
var Clicker = React.createClass({
getInitialState: function() {
return {count: 0};
},
render: function() {
return <span onClick={this.handleClick}>Click me!</span>;
},
handleClick: React.autoBind(function() {
this.setState({count: this.state.count + 1});
if (this.props.onCountChange) {
this.props.onCountChange(this.state.count);
}
})
});
var ClickCountLabel = React.createClass({
render: function() {
return <p>You have clicked <strong>{this.props.count}</strong> times.</p>;
}
});
var ClickApp = React.createClass({
render: function() {
var count = 0;
return (
<div>
<Clicker onCountChange={this.handleCountChange} />
<ClickCountLabel count={count} />
</div>
);
},
handleCountChange: React.autoBind(function(count) {
// Somehow update `count`.
})
});
```
In order to communicate the click count from `Clicker` to `ClickCountLabel`, we
modify `ClickApp` to maintain state that will be passed into `ClickCountLabel`:
```javascript{2-4,6,15}
var ClickApp = React.createClass({
getInitialState: function() {
return {count: 0};
},
render: function() {
var count = this.state.count;
return (
<div>
<Clicker onCountChange={this.handleCountChange} />
<ClickCountLabel count={count} />
</div>
);
},
handleCountChange: React.autoBind(function(count) {
this.setState({count: count});
})
});
```
Now when `Clicker` fires the `onCountChange` event, the `ClickCountLabel` will
get updated!

View File

@@ -1,5 +1,5 @@
---
id: docs-getting-started
id: getting-started
title: Getting Started
layout: docs
next: tutorial.html

View File

@@ -1,65 +0,0 @@
---
id: docs-mixins
title: Mixins
layout: docs
prev: advanced-components.html
next: api.html
---
Mixins allow code to be shared between multiple React components. They are pretty similar to mixins
in Python or traits in PHP. Let's look at a simple example:
```javascript
var MyMixin = {
getMessage: function() {
return 'hello world';
}
};
var MyComponent = React.createClass({
mixins: [MyMixin],
render: function() {
return <div>{this.getMessage()}</div>;
}
});
```
A class can use multiple mixins, but no two mixins can define the same method. Two mixins can, however,
implement the same [lifecycle method](component-lifecycle.html). In this case, each implementation will be invoked one after another.
The only exception is the `shouldComponentUpdate` lifecycle method. This method may only be implemented once
(either by a mixin or by the component).
```javascript
var Mixin1 = {
componentDidMount: function() {
console.log('Mixin1.componentDidMount()');
}
};
var Mixin2 = {
componentDidMount: function() {
console.log('Mixin2.componentDidMount()');
}
};
var MyComponent = React.createClass({
mixins: [Mixin1, Mixin2],
render: function() {
return <div>hello world</div>;
}
});
```
When `MyComponent` is mounted into the page, the following text will print to the console:
```
Mixin1.componentDidMount()
Mixin2.componentDidMount()
```
## When should you use mixins?
In general, add a mixin whenever you want a component to share some utility methods, public interface,
or lifecycle behavior. Often it's appropriate to use them as you would use a superclass in another OOP language.

View File

@@ -1,11 +1,8 @@
---
id: docs-tutorial
id: tutorial
title: Tutorial
layout: docs
prev: getting-started.html
next: common-questions.html
---
We'll be building a simple, but realistic comments box that you can drop into a blog, similar to Disqus, LiveFyre or Facebook comments.
We'll provide:
@@ -20,11 +17,11 @@ It'll also have a few neat features:
* **Live updates:** as other users comment we'll pop them into the comment view in real time
* **Markdown formatting:** users can use Markdown to format their text
## Want to skip all this and just see the source?
### Want to skip all this and just see the source?
[It's all on GitHub.](https://github.com/petehunt/react-tutorial)
## Getting started
### Getting started
For this tutorial we'll use prebuilt JavaScript files on a CDN. Open up your favorite editor and create a new HTML document:
@@ -50,7 +47,7 @@ For this tutorial we'll use prebuilt JavaScript files on a CDN. Open up your fav
For the remainder of this tutorial, we'll be writing our JavaScript code in this script tag.
## Your first component
### Your first component
React is all about modular, composable components. For our comment box example, we'll have the following component structure:
@@ -80,7 +77,7 @@ React.renderComponent(
);
```
### JSX Syntax
#### JSX Syntax
The first thing you'll notice is the XML-ish syntax in your JavaScript. We have a simple precompiler that translates the syntactic sugar to this plain JavaScript:
@@ -104,7 +101,7 @@ React.renderComponent(
Its use is optional but we've found JSX syntax easier to use than plain JavaScript. Read more on the [JSX Syntax article](syntax.html).
### What's going on
#### What's going on
We pass some methods in a JavaScript object to `React.createClass()` to create a new React component. The most important of these methods is called `render` which returns a tree of React components that will eventually render to HTML.
@@ -114,7 +111,7 @@ You do not have to return basic HTML. You can return a tree of components that y
`React.renderComponent()` instantiates the root component, starts the framework, and injects the markup into a raw DOM element, provided as the second argument.
# Composing components
## Composing components
Let's build skeletons for `CommentList` and `CommentForm` which will, again, be simple `<div>`s:
@@ -160,7 +157,7 @@ var CommentBox = React.createClass({
Notice how we're mixing HTML tags and components we've built. HTML components are regular React components, just like the ones you define, with one difference. The JSX compiler will automatically rewrite HTML tags to "React.DOM.tagName" expressions and leave everything else alone. This is to prevent the pollution of the global namespace.
## Component Properties
### Component Properties
Let's create our third component, `Comment`. We will want to pass it the author name and comment text so we can reuse the same code for each unique comment. First let's add some comments to the `CommentList`:
@@ -180,7 +177,7 @@ var CommentList = React.createClass({
Note that we have passed some data from the parent `CommentList` component to the child `Comment` component as both XML-like children and attributes. Data passed from parent to child is called **props**, short for properties.
## Using props
### Using props
Let's create the Comment component. It will read the data passed to it from the CommentList and render some markup:
@@ -202,7 +199,7 @@ var Comment = React.createClass({
By surrounding a JavaScript expression in braces inside JSX (as either an attribute or child), you can drop text or React components into the tree. We access named attributes passed to the component as keys on `this.props` and any nested elements as `this.props.children`.
## Adding Markdown
### Adding Markdown
Markdown is a simple way to format your text inline. For example, surrounding text with asterisks will make it emphasized.
@@ -255,7 +252,7 @@ This is a special API that intentionally makes it difficult to insert raw HTML,
**Remember:** by using this feature you're relying on Showdown to be secure.
## Hook up the data model
### Hook up the data model
So far we've been inserting the comments directly in the source code. Instead, let's render a blob of JSON data into the comment list. Eventually this will come from the server, but for now, write it in your source:
@@ -309,7 +306,7 @@ var CommentList = React.createClass({
That's it!
## Fetching from the server
### Fetching from the server
Let's replace the hard-coded data with some dynamic data from the server. We will remove the data prop and replace it with a URL to fetch:
@@ -323,7 +320,7 @@ React.renderComponent(
This component is different from the prior components because it will have to re-render itself. The component won't have any data until the request from the server comes back, at which point the component may need to render some new comments.
## Reactive state
### Reactive state
So far, each component has rendered itself once based on its props. `props` are immutable: they are passed from the parent and are "owned" by the parent. To implement interactions, we introduce mutable **state** to the component. `this.state` is private to the component and can be changed by calling `this.setState()`. When the state is updated, the component re-renders itself.
@@ -351,7 +348,7 @@ var CommentBox = React.createClass({
`getInitialState()` executes exactly once during the lifecycle of the component and sets up the initial state of the component.
### Updating state
#### Updating state
When the component is first created, we want to GET some JSON from the server and update the state to reflect the latest data. In a real application this would be a dynamic endpoint, but for this example, we will use a static JSON file to keep things simple:
```javascript
@@ -437,7 +434,7 @@ React.renderComponent(
All we have done here is move the AJAX call to a separate method and call it when the component is first loaded and every 5 seconds after that. Try running this in your browser and changing the `comments.json` file; within 5 seconds, the changes will show!
## Adding new comments
### Adding new comments
Now it's time to build the form. Our `CommentForm` component should ask the user for their name and comment text and send a request to the server to save the comment.
@@ -461,7 +458,7 @@ Let's make the form interactive. When the user submits the form, we should clear
```javascript{3-13,16,21}
// tutorial16.js
var CommentForm = React.createClass({
handleSubmit: React.autoBind(function() {
handleSubmit: function() {
var author = this.refs.author.getDOMNode().value.trim();
var text = this.refs.text.getDOMNode().value.trim();
if (!text || !author) {
@@ -471,7 +468,7 @@ var CommentForm = React.createClass({
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
return false;
}),
},
render: function() {
return (
<form class="commentForm" onSubmit={this.handleSubmit}>
@@ -488,19 +485,17 @@ var CommentForm = React.createClass({
});
```
#### Events
##### Events
React attaches event handlers to components using a camelCase naming convention. We attach an `onSubmit` handler to the form that clears the form fields when the form is submitted with valid input.
We always return `false` from the event handler to prevent the browser's default action of submitting the form. (If you prefer, you can instead take the event as an argument and call `preventDefault()` on it &ndash; read more about [event handling](event-handling.html).)
`React.autoBind()` is a simple way to ensure that a method is always bound to its component. Inside the method, `this` will be bound to the component instance.
#### Refs
##### Refs
We use the `ref` attribute to assign a name to a child component and `this.refs` to reference the component. We can call `getDOMNode()` on a component to get the native browser DOM element.
#### Callbacks as props
##### Callbacks as props
When a user submits a comment, we will need to refresh the list of comments to include the new one. It makes sense to do all of this logic in `CommentBox` since `CommentBox` owns the state that represents the list of comments.
@@ -519,9 +514,9 @@ var CommentBox = React.createClass({
}.bind(this)
});
},
handleCommentSubmit: React.autoBind(function(comment) {
handleCommentSubmit: function(comment) {
// TODO: submit to the server and refresh the list
}),
},
getInitialState: function() {
return {data: []};
},
@@ -551,14 +546,14 @@ Let's call the callback from the `CommentForm` when the user submits the form:
```javascript{6}
// tutorial18.js
var CommentForm = React.createClass({
handleSubmit: React.autoBind(function() {
handleSubmit: function() {
var author = this.refs.author.getDOMNode().value.trim();
var text = this.refs.text.getDOMNode().value.trim();
this.props.onCommentSubmit({author: author, text: text});
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
return false;
}),
},
render: function() {
return (
<form class="commentForm" onSubmit={this.handleSubmit}>
@@ -590,7 +585,7 @@ var CommentBox = React.createClass({
}.bind(this)
});
},
handleCommentSubmit: React.autoBind(function(comment) {
handleCommentSubmit: function(comment) {
$.ajax({
url: this.props.url,
data: comment,
@@ -600,7 +595,7 @@ var CommentBox = React.createClass({
this.setState({data: data});
}.bind(this)
});
}),
},
getInitialState: function() {
return {data: []};
},
@@ -625,7 +620,7 @@ var CommentBox = React.createClass({
});
```
## Optimization: optimistic updates
### Optimization: optimistic updates
Our application is now feature complete but it feels slow to have to wait for the request to complete before your comment appears in the list. We can optimistically add this comment to the list to make the app feel faster.
@@ -642,7 +637,7 @@ var CommentBox = React.createClass({
}.bind(this)
});
},
handleCommentSubmit: React.autoBind(function(comment) {
handleCommentSubmit: function(comment) {
var comments = this.state.data;
comments.push(comment);
this.setState({data: comments});
@@ -655,7 +650,7 @@ var CommentBox = React.createClass({
this.setState({data: data});
}.bind(this)
});
}),
},
getInitialState: function() {
return {data: []};
},
@@ -680,6 +675,6 @@ var CommentBox = React.createClass({
});
```
## Congrats!
### Congrats!
You have just built a comment box in a few simple steps. Learn more about React in the [reference](syntax.html) or start hacking! Good luck!

View File

@@ -47,16 +47,3 @@ $ bower install --save react
$ npm install -g react-tools
```
## Release Notes
**0.3.3** Upgrade `react-tools` dependencies, improve in-browser JSX transformer, make `React.renderComponent` more versatile. [Details...](blog/2013/06/20/react-v0-3-3.html)
**0.3.2** Improve compatibility of JSX Transformer; make `react-tools` compatible with [browserify](https://github.com/substack/node-browserify)
**0.3.1** Fix `react-tools` module
**0.3** Initial public release.
**0.2** Standardize API & refactor component lifecycle. Normalize DOM interactions.
**0.1** Initial release.

View File

@@ -14,9 +14,9 @@ var BallmerPeakCalculator = React.createClass({
getInitialState: function() {
return {bac: 0};
},
handleChange: React.autoBind(function() {
handleChange: function() {
this.setState({bac: this.refs.bac.getDOMNode().value});
}),
},
render: function() {
var bac;
var pct;
@@ -33,7 +33,7 @@ var BallmerPeakCalculator = React.createClass({
<h4>Compute your Ballmer Peak:</h4>
<p>
If your BAC is{' '}
<input ref="bac" type="text" onKeyUp={this.handleChange} value={this.state.bac} />
<input ref="bac" type="text" onInput={this.handleChange} value={this.state.bac} />
{', '}then <b>{pct}</b> of your lines of code will have bugs.
</p>
</div>

View File

@@ -67,18 +67,18 @@ var BootstrapModal = React.createClass({
</div>
);
},
onCancel: React.autoBind(function() {
onCancel: function() {
if (this.props.onCancel) {
this.props.onCancel();
}
this.close();
}),
onConfirm: React.autoBind(function() {
},
onConfirm: function() {
if (this.props.onConfirm) {
this.props.onConfirm();
}
this.close();
}),
},
close: function() {
if (this.props.onClose) {
this.props.onClose();
@@ -90,14 +90,14 @@ var Example = React.createClass({
getInitialState: function() {
return {modalVisible: false};
},
toggleModal: React.autoBind(function() {
toggleModal: function() {
this.setState({modalVisible: !this.state.modalVisible});
}),
handleCancel: React.autoBind(function() {
},
handleCancel: function() {
if (confirm('Are you sure you want to cancel?')) {
this.toggleModal();
}
}),
},
render: function() {
var modal = null;
if (this.state.modalVisible) {
@@ -121,4 +121,4 @@ var Example = React.createClass({
}
});
React.renderComponent(<Example />, document.getElementById('jqueryexample'));
React.renderComponent(<Example />, document.getElementById('jqueryexample'));

View File

@@ -74,17 +74,17 @@ var Utils = {
// Begin React stuff
var TodoItem = React.createClass({
handleSubmit: React.autoBind(function(event) {
handleSubmit: function(event) {
var val = this.refs.editField.getDOMNode().value;
if (val) {
this.props.onSave(val);
}
return false;
}),
onEdit: React.autoBind(function() {
},
onEdit: function() {
this.props.onEdit();
this.refs.editField.getDOMNode().focus();
}),
},
render: function() {
return (
<li class={cx({completed: this.props.todo.get('completed'), editing: this.props.editing})}>
@@ -166,7 +166,7 @@ var TodoApp = React.createClass({
getBackboneModels: function() {
return [this.props.todos];
},
handleSubmit: React.autoBind(function() {
handleSubmit: function() {
var val = this.refs.newField.getDOMNode().value.trim();
if (val) {
this.props.todos.create({
@@ -177,13 +177,13 @@ var TodoApp = React.createClass({
this.refs.newField.getDOMNode().value = '';
}
return false;
}),
toggleAll: React.autoBind(function(event) {
},
toggleAll: function(event) {
var checked = event.nativeEvent.target.checked;
this.props.todos.map(function(todo) {
todo.set('completed', checked);
});
}),
},
destroy: function(todo) {
this.props.todos.remove(todo);
},
@@ -194,11 +194,11 @@ var TodoApp = React.createClass({
todo.set('title', text);
this.setState({editing: null});
},
clearCompleted: React.autoBind(function() {
clearCompleted: function() {
this.props.todos.completed().map(function(todo) {
todo.destroy();
});
}),
},
render: function() {
var footer = null;
var main = null;

View File

@@ -32,24 +32,24 @@ function cx(obj) {
}
var TodoItem = React.createClass({
handleSubmit: React.autoBind(function() {
handleSubmit: function() {
var val = this.state.editText;
if (val) {
this.props.onSave(val);
this.setState({editField: ''});
}
return false;
}),
handleEdit: React.autoBind(function() {
},
handleEdit: function() {
this.props.onEdit();
this.refs.editField.getDOMNode().focus();
}),
handleKey: React.autoBind(function(event) {
},
handleKey: function(event) {
if (event.nativeEvent.keyCode === 27) {
this.handleSubmit();
}
this.setState({editText: event.target.value});
}),
},
getInitialState: function() {
return {editText: this.props.todo.title};
},
@@ -114,7 +114,7 @@ var TodoApp = React.createClass({
};
},
handleSubmit: React.autoBind(function() {
handleSubmit: function() {
var val = this.refs.newField.getDOMNode().value.trim();
if (val) {
var todos = this.state.todos;
@@ -127,7 +127,7 @@ var TodoApp = React.createClass({
this.refs.newField.getDOMNode().value = '';
}
return false;
}),
},
toggleAll: function(event) {
var checked = event.nativeEvent.target.checked;

View File

@@ -76,33 +76,8 @@ var transformer = {
after: [simpleBannerify]
};
var jasmine = {
entries: [
"./build/jasmine/all.js"
],
requires: {
"jasmine": "./build/jasmine/all.js"
},
outfile: "./build/jasmine.js",
debug: false
};
var test = {
entries: [
"./build/modules/test/all.js",
],
requires: [
"**/__tests__/*-test.js"
],
outfile: './build/react-test.js',
debug: false,
standalone: false
};
module.exports = {
basic: basic,
jasmine: jasmine,
test: test,
min: min,
transformer: transformer
};

View File

@@ -12,6 +12,6 @@ module.exports = {
return gzip.zip(contents, {}).length;
}
},
cache: "build/.sizecache.json"
cache: ".grunt/sizecache.json"
}
};

View File

@@ -25,7 +25,7 @@ var test = {
"test/all.js",
"**/__tests__/*.js"
]),
configFile: debug.configFile,
configFile: "grunt/config/jsx/test.json",
sourceDir: "src",
outputDir: "build/modules"
};

View File

@@ -0,0 +1,7 @@
{
"debug": true,
"mocking": true,
"constants": {
"__DEV__": true
}
}

24
grunt/config/populist.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
var jasmine = {
rootDirectory: "build/jasmine",
// This syntax means to require and expose the "jasmine" module
// (build/jasmine/jasmine.js) as global.jasmine, and to require the
// "all" module (build/jasmine/all.js) but not expose it globally.
args: ["jasmine:jasmine", "all:"],
outfile: "./build/jasmine.js"
};
var test = {
rootDirectory: "build/modules",
args: ["test/all:"],
requires: [
"**/__tests__/*-test.js"
],
outfile: './build/react-test.js'
};
module.exports = {
jasmine: jasmine,
test: test
};

View File

@@ -1,16 +1,17 @@
'use strict';
var exec = require("child_process").exec;
var expand = require("grunt").file.expand;
var grunt = require("grunt");
var expand = grunt.file.expand;
var spawn = grunt.util.spawn;
module.exports = function() {
var done = this.async();
var config = this.data;
var args = [
"bin/jsx",
"--cache-dir", ".module-cache",
"--relativize",
"--follow-requires",
config.sourceDir,
config.outputDir
];
@@ -25,5 +26,18 @@ module.exports = function() {
args.push.apply(args, rootIDs);
args.push("--config", config.configFile);
exec(args.join(" "), done);
var child = spawn({
cmd: "bin/jsx",
args: args
}, function(error, result, code) {
if (error) {
grunt.log.error(error);
done(false);
} else {
done();
}
});
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
};

View File

@@ -2,13 +2,10 @@
var assert = require("assert");
var path = require("path");
var fs = require("fs");
var tmp = require("tmp");
var grunt = require("grunt");
var spawn = grunt.util.spawn;
module.exports = function() {
var config = this.data;
var done = this.async();
function run(cmd, args, opts, callback) {
@@ -25,7 +22,8 @@ module.exports = function() {
grunt.log.writeln("> " + cmd + " " + args.join(" "));
var proc = spawn({
// var proc =
spawn({
cmd: cmd,
args: args,
opts: opts
@@ -49,7 +47,7 @@ module.exports = function() {
grunt.log.writeln("Packing " + tgz + " (this could take a while)...");
run("npm", ["pack", "--verbose", "."], function() {
tmp.dir(function(err, dir) {
require("tmp").dir(function(err, dir) {
if (err) {
grunt.log.error(err);
done(false);

View File

@@ -1,6 +1,7 @@
'use strict';
var assert = require("assert");
var fs = require("fs");
var grunt = require("grunt");
var spawn = grunt.util.spawn;
var semver = require("semver");
@@ -38,7 +39,7 @@ function run(config, done) {
}
args.push("--tests");
var tests = grunt.file.expand({
grunt.file.expand({
nonull: true,
cwd: "src"
}, config.tests || []).forEach(function(file) {
@@ -58,11 +59,13 @@ module.exports = function() {
var config = this.data;
var done = this.async();
spawn({
cmd: phantomjs,
args: ["--version"]
}, function(error, result, code) {
checkVersion(error, result, code);
run(config, done);
fs.chmod(phantomjs, 755, function(err) {
spawn({
cmd: phantomjs,
args: ["--version"]
}, function(error, result, code) {
checkVersion(error, result, code);
run(config, done);
});
});
};

28
grunt/tasks/populist.js Normal file
View File

@@ -0,0 +1,28 @@
'use strict';
var grunt = require('grunt');
module.exports = function() {
var config = this.data;
var done = this.async();
// create the bundle we'll work with
var args = config.args;
// Make sure the things that need to be exposed are.
var requires = config.requires || [];
grunt.file.expand({
nonull: true, // Keep IDs that don't expand to anything.
cwd: config.rootDirectory
}, requires).forEach(function(name) {
args.push(name.replace(/\.js$/i, ""));
});
require("populist").buildP({
rootDirectory: config.rootDirectory,
args: args
}).then(function(output) {
grunt.file.write(config.outfile, output);
done();
});
};

View File

@@ -1,6 +1,6 @@
{
"name": "react-tools",
"version": "0.3.3",
"version": "0.4.0",
"keywords": [
"react",
"jsx",
@@ -21,7 +21,7 @@
"bin/jsx",
"build/modules/",
"vendor/fbtransform/",
"vendor/woodchipper.js"
"vendor/constants.js"
],
"main": "main.js",
"bin": {
@@ -36,21 +36,22 @@
},
"dependencies": {
"base62": "~0.1.1",
"commoner": "~0.7.0",
"esprima": "https://github.com/facebook/esprima/tarball/ca28795124d45968e62a7b4b336d23a053ac3a84",
"commoner": "~0.8.4",
"esprima": "https://github.com/facebook/esprima/tarball/a3e0ea3979eb8d54d8bfade220c272903f928b1e",
"recast": "~0.4.8",
"source-map": "~0.1.22"
},
"devDependencies": {
"browserify": "~2.14.2",
"browserify": "~2.24.1",
"wrapup": "~0.12.0",
"populist": "~0.1.3",
"grunt-cli": "~0.1.9",
"grunt": "~0.4.1",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jshint": "~0.5.4",
"grunt-contrib-jshint": "~0.6.0",
"optimist": "~0.4.0",
"phantomjs": ">= 1.9.0",
"semver": ">= 1.1.4",
"semver": "~2.0.0",
"uglify-js": "~2.3.6",
"grunt-contrib-clean": "~0.4.1",
"grunt-compare-size": "~0.4.0",
@@ -58,5 +59,8 @@
"tmp": "~0.0.18",
"grunt-contrib-compress": "~0.5.1"
},
"engines": {
"node": ">=0.10.0"
},
"preferGlobal": true
}

View File

@@ -21,6 +21,7 @@
"globals": {
"__DEV__": false,
"require": false,
"module": false
"module": false,
"exports": false
}
}

View File

@@ -22,6 +22,8 @@ var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactComponent = require('ReactComponent');
var ReactDOM = require('ReactDOM');
var ReactMount = require('ReactMount');
var ReactPropTypes = require('ReactPropTypes');
var ReactServerRendering = require('ReactServerRendering');
var ReactDefaultInjection = require('ReactDefaultInjection');
@@ -29,15 +31,16 @@ ReactDefaultInjection.inject();
var React = {
DOM: ReactDOM,
PropTypes: ReactPropTypes,
initializeTouchEvents: function(shouldUseTouch) {
ReactMount.useTouchEvents = shouldUseTouch;
},
autoBind: ReactCompositeComponent.autoBind,
createClass: ReactCompositeComponent.createClass,
createComponentRenderer: ReactMount.createComponentRenderer,
constructAndRenderComponent: ReactMount.constructAndRenderComponent,
constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
renderComponent: ReactMount.renderComponent,
renderComponentToString: ReactServerRendering.renderComponentToString,
unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
isValidComponent: ReactComponent.isValidComponent
};

View File

@@ -16,14 +16,17 @@
* @providesModule ReactComponent
*/
/*jslint evil: true */
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactID = require('ReactID');
var ReactMount = require('ReactMount');
var ReactOwner = require('ReactOwner');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
var ReactUpdates = require('ReactUpdates');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
@@ -35,6 +38,12 @@ var merge = require('merge');
*/
var OWNER = '{owner}';
/**
* Props key that determines if a component's key was already validated.
* @private
*/
var IS_KEY_VALIDATED = '{is.key.validated}';
/**
* Every React component is in one of these life cycles.
*/
@@ -50,6 +59,78 @@ var ComponentLifeCycle = keyMirror({
UNMOUNTED: null
});
/**
* Warn if there's no key explicitly set on dynamic arrays of children.
* This allows us to keep track of children between updates.
*/
var ownerHasWarned = {};
/**
* Warn if the component doesn't have an explicit key assigned to it.
* This component is in an array. The array could grow and shrink or be
* reordered. All children, that hasn't already been validated, are required to
* have a "key" property assigned to it.
*
* @internal
* @param {ReactComponent} component Component that requires a key.
*/
function validateExplicitKey(component) {
if (component[IS_KEY_VALIDATED] || component.props.key != null) {
return;
}
component[IS_KEY_VALIDATED] = true;
// We can't provide friendly warnings for top level components.
if (!ReactCurrentOwner.current) {
return;
}
// Name of the component whose render method tried to pass children.
var currentName = ReactCurrentOwner.current.constructor.displayName;
if (ownerHasWarned.hasOwnProperty(currentName)) {
return;
}
ownerHasWarned[currentName] = true;
var message = 'Each child in an array should have a unique "key" prop. ' +
'Check the render method of ' + currentName + '.';
if (!component.isOwnedBy(ReactCurrentOwner.current)) {
// Name of the component that originally created this child.
var childOwnerName =
component.props[OWNER] && component.props[OWNER].constructor.displayName;
// Usually the current owner is the offender, but if it accepts
// children as a property, it may be the creator of the child that's
// responsible for assigning it a key.
message += ' It was passed a child from ' + childOwnerName + '.';
}
console.warn(message);
}
/**
* 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.
*
* @internal
* @param {*} component Statically passed child of any type.
* @return {boolean}
*/
function validateChildKeys(component) {
if (Array.isArray(component)) {
for (var i = 0; i < component.length; i++) {
var child = component[i];
if (ReactComponent.isValidComponent(child)) {
validateExplicitKey(child);
}
}
} else if (ReactComponent.isValidComponent(component)) {
// This component was passed in a valid location.
component[IS_KEY_VALIDATED] = true;
}
}
/**
* Components are the basic units of composition in React.
*
@@ -90,6 +171,23 @@ var ReactComponent = {
);
},
/**
* Generate a key string that identifies a component within a set.
*
* @param {*} component A component that could contain a manual key.
* @param {number} index Index that is used if a manual key is not provided.
* @return {string}
* @internal
*/
getKey: function(component, index) {
if (component && component.props && component.props.key != null) {
// Explicit key
return '' + component.props.key;
}
// Implicit key determined by the index in the set
return '' + index;
},
/**
* @internal
*/
@@ -134,53 +232,57 @@ var ReactComponent = {
*/
Mixin: {
/**
* Checks whether or not this component is mounted.
*
* @return {boolean} True if mounted, false otherwise.
* @final
* @protected
*/
isMounted: function() {
return this._lifeCycleState === ComponentLifeCycle.MOUNTED;
},
/**
* Returns the DOM node rendered by this component.
*
* @return {?DOMElement} The root node of this component.
* @return {DOMElement} The root node of this component.
* @final
* @protected
*/
getDOMNode: function() {
invariant(
ExecutionEnvironment.canUseDOM,
'getDOMNode(): The DOM is not supported in the current environment.'
);
invariant(
this._lifeCycleState === ComponentLifeCycle.MOUNTED,
this.isMounted(),
'getDOMNode(): A component must be mounted to have a DOM node.'
);
var rootNode = this._rootNode;
if (!rootNode) {
rootNode = document.getElementById(this._rootNodeID);
if (!rootNode) {
// TODO: Log the frequency that we reach this path.
rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
}
this._rootNode = rootNode;
}
return rootNode;
return ReactID.getNode(this._rootNodeID);
},
/**
* Sets a subset of the props.
*
* @param {object} partialProps Subset of the next props.
* @param {?function} callback Called after props are updated.
* @final
* @public
*/
setProps: function(partialProps) {
this.replaceProps(merge(this.props, partialProps));
setProps: function(partialProps, callback) {
// Merge with `_pendingProps` if it exists, otherwise with existing props.
this.replaceProps(
merge(this._pendingProps || this.props, partialProps),
callback
);
},
/**
* Replaces all of the props.
*
* @param {object} props New props.
* @param {?function} callback Called after props are updated.
* @final
* @public
*/
replaceProps: function(props) {
replaceProps: function(props, callback) {
invariant(
!this.props[OWNER],
'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
@@ -189,9 +291,8 @@ var ReactComponent = {
'`render` method to pass the correct value as props to the component ' +
'where it is created.'
);
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(this.receiveProps, this, props, transaction);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._pendingProps = props;
ReactUpdates.enqueueUpdate(this, callback);
},
/**
@@ -206,13 +307,31 @@ var ReactComponent = {
*/
construct: function(initialProps, children) {
this.props = initialProps || {};
if (typeof children !== 'undefined') {
this.props.children = children;
}
// Record the component responsible for creating this component.
this.props[OWNER] = ReactCurrentOwner.current;
// All components start unmounted.
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
this._pendingProps = null;
this._pendingCallbacks = null;
// Children can be more than one argument
var childrenLength = arguments.length - 1;
if (childrenLength === 1) {
if (__DEV__) {
validateChildKeys(children);
}
this.props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
if (__DEV__) {
validateChildKeys(arguments[i + 1]);
}
childArray[i] = arguments[i + 1];
}
this.props.children = childArray;
}
},
/**
@@ -230,7 +349,7 @@ var ReactComponent = {
*/
mountComponent: function(rootID, transaction) {
invariant(
this._lifeCycleState === ComponentLifeCycle.UNMOUNTED,
!this.isMounted(),
'mountComponent(%s, ...): Can only mount an unmounted component.',
rootID
);
@@ -255,14 +374,14 @@ var ReactComponent = {
*/
unmountComponent: function() {
invariant(
this._lifeCycleState === ComponentLifeCycle.MOUNTED,
this.isMounted(),
'unmountComponent(): Can only unmount a mounted component.'
);
var props = this.props;
if (props.ref != null) {
ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
}
this._rootNode = null;
ReactID.purgeID(this._rootNodeID);
this._rootNodeID = null;
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
},
@@ -279,20 +398,62 @@ var ReactComponent = {
*/
receiveProps: function(nextProps, transaction) {
invariant(
this._lifeCycleState === ComponentLifeCycle.MOUNTED,
this.isMounted(),
'receiveProps(...): Can only update a mounted component.'
);
this._pendingProps = nextProps;
this._performUpdateIfNecessary(transaction);
},
/**
* Call `_performUpdateIfNecessary` within a new transaction.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
performUpdateIfNecessary: function() {
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(this._performUpdateIfNecessary, this, transaction);
ReactComponent.ReactReconcileTransaction.release(transaction);
},
/**
* If `_pendingProps` is set, update the component.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
_performUpdateIfNecessary: function(transaction) {
if (this._pendingProps == null) {
return;
}
var prevProps = this.props;
this.props = this._pendingProps;
this._pendingProps = null;
this.updateComponent(transaction, prevProps);
},
/**
* Updates the component's currently mounted representation.
*
* @param {ReactReconcileTransaction} transaction
* @param {object} prevProps
* @internal
*/
updateComponent: function(transaction, prevProps) {
var props = this.props;
// If either the owner or a `ref` has changed, make sure the newest owner
// has stored a reference to `this`, and the previous owner (if different)
// has forgotten the reference to `this`.
if (nextProps[OWNER] !== props[OWNER] || nextProps.ref !== props.ref) {
if (props.ref != null) {
ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
if (props[OWNER] !== prevProps[OWNER] || props.ref !== prevProps.ref) {
if (prevProps.ref != null) {
ReactOwner.removeComponentAsRefFrom(
this, prevProps.ref, prevProps[OWNER]
);
}
// Correct, even if the owner is the same, and only the ref has changed.
if (nextProps.ref != null) {
ReactOwner.addComponentAsRefTo(this, nextProps.ref, nextProps[OWNER]);
if (props.ref != null) {
ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
}
}
},
@@ -302,18 +463,20 @@ var ReactComponent = {
*
* @param {string} rootID DOM ID of the root node.
* @param {DOMElement} container DOM element to mount into.
* @param {boolean} shouldReuseMarkup If true, do not insert markup
* @final
* @internal
* @see {ReactMount.renderComponent}
*/
mountComponentIntoNode: function(rootID, container) {
mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) {
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._mountComponentIntoNode,
this,
rootID,
container,
transaction
transaction,
shouldReuseMarkup
);
ReactComponent.ReactReconcileTransaction.release(transaction);
},
@@ -322,14 +485,27 @@ var ReactComponent = {
* @param {string} rootID DOM ID of the root node.
* @param {DOMElement} container DOM element to mount into.
* @param {ReactReconcileTransaction} transaction
* @param {boolean} shouldReuseMarkup If true, do not insert markup
* @final
* @private
*/
_mountComponentIntoNode: function(rootID, container, transaction) {
_mountComponentIntoNode: function(
rootID,
container,
transaction,
shouldReuseMarkup) {
invariant(
container && container.nodeType === 1,
'mountComponentIntoNode(...): Target container is not a DOM element.'
);
var renderStart = Date.now();
var markup = this.mountComponent(rootID, transaction);
ReactMount.totalInstantiationTime += (Date.now() - renderStart);
if (shouldReuseMarkup) {
return;
}
var injectionStart = Date.now();
// Asynchronously inject markup by ensuring that the container is not in
// the document when settings its `innerHTML`.
@@ -375,30 +551,26 @@ var ReactComponent = {
*/
isOwnedBy: function(owner) {
return this.props[OWNER] === owner;
},
/**
* Gets another component, that shares the same owner as this one, by ref.
*
* @param {string} ref of a sibling Component.
* @return {?ReactComponent} the actual sibling Component.
* @final
* @internal
*/
getSiblingByRef: function(ref) {
var owner = this.props[OWNER];
if (!owner || !owner.refs) {
return null;
}
return owner.refs[ref];
}
}
};
function logDeprecated(msg) {
if (__DEV__) {
throw new Error(msg);
} else {
console && console.warn && console.warn(msg);
}
}
/**
* @deprecated
*/
ReactComponent.Mixin.update = function(props) {
logDeprecated('this.update() is deprecated. Use this.setProps()');
this.setProps(props);
};
ReactComponent.Mixin.updateAll = function(props) {
logDeprecated('this.updateAll() is deprecated. Use this.replaceProps()');
this.replaceProps(props);
};
module.exports = ReactComponent;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactComponentWithPureRender
* @providesModule ReactComponentWithPureRenderMixin
*/
"use strict";
@@ -27,9 +27,10 @@
*
* Example:
*
* var ReactComponentWithPureRender = require('ReactComponentWithPureRender');
* var ReactComponentWithPureRender =
* require('ReactComponentWithPureRenderMixin');
* React.createClass({
* mixins: [ReactComponentWithPureRender],
* mixins: [ReactComponentWithPureRenderMixin],
*
* render: function() {
* return <div className={this.props.className}>foo</div>;
@@ -41,7 +42,7 @@
* differences. Only mixin to components which have simple props and state, or
* use `forceUpdate()` when you know deep data structures have changed.
*/
var ReactComponentWithPureRender = {
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
@@ -74,4 +75,4 @@ function shallowEqual(objA, objB) {
return true;
}
module.exports = ReactComponentWithPureRender;
module.exports = ReactComponentWithPureRenderMixin;

View File

@@ -22,6 +22,7 @@ var ReactComponent = require('ReactComponent');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactOwner = require('ReactOwner');
var ReactPropTransferer = require('ReactPropTransferer');
var ReactUpdates = require('ReactUpdates');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
@@ -80,17 +81,29 @@ var ReactCompositeComponentInterface = {
mixins: SpecPolicy.DEFINE_MANY,
/**
* Definition of props for this component.
* Definition of prop types for this component.
*
* @type {array}
* @type {object}
* @optional
*/
props: SpecPolicy.DEFINE_ONCE,
propTypes: SpecPolicy.DEFINE_ONCE,
// ==== Definition methods ====
/**
* Invoked when the component is mounted. Values in the mapping will be set on
* `this.props` if that prop is not specified (i.e. using an `in` check).
*
* This method is invoked before `getInitialState` and therefore cannot rely
* on `this.state` or use `this.setState`.
*
* @return {object}
* @optional
*/
getDefaultProps: SpecPolicy.DEFINE_ONCE,
/**
* Invoked once before the component is mounted. The return value will be used
* as the initial value of `this.state`.
@@ -265,11 +278,53 @@ var RESERVED_SPEC_KEYS = {
}
}
},
props: function(Constructor, props) {
Constructor.propDeclarations = props;
propTypes: function(Constructor, propTypes) {
Constructor.propTypes = propTypes;
}
};
function validateMethodOverride(proto, name) {
var specPolicy = ReactCompositeComponentInterface[name];
// Disallow overriding of base class methods unless explicitly allowed.
if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.OVERRIDE_BASE,
'ReactCompositeComponentInterface: You are attempting to override ' +
'`%s` from your class specification. Ensure that your method names ' +
'do not overlap with React methods.',
name
);
}
// Disallow defining methods more than once unless explicitly allowed.
if (proto.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.DEFINE_MANY,
'ReactCompositeComponentInterface: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be due ' +
'to a mixin.',
name
);
}
}
function validateLifeCycleOnReplaceState(instance) {
var compositeLifeCycleState = instance._compositeLifeCycleState;
invariant(
instance.isMounted() ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted or mounting component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
}
/**
* Custom version of `mixInto` which handles policy validation and reserved
* specification keys when building `ReactCompositeComponent` classses.
@@ -277,58 +332,44 @@ var RESERVED_SPEC_KEYS = {
function mixSpecIntoComponent(Constructor, spec) {
var proto = Constructor.prototype;
for (var name in spec) {
if (!spec.hasOwnProperty(name)) {
var property = spec[name];
if (!spec.hasOwnProperty(name) || !property) {
continue;
}
var property = spec[name];
var specPolicy = ReactCompositeComponentInterface[name];
// Disallow overriding of base class methods unless explicitly allowed.
if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.OVERRIDE_BASE,
'ReactCompositeComponentInterface: You are attempting to override ' +
'`%s` from your class specification. Ensure that your method names ' +
'do not overlap with React methods.',
name
);
}
// Disallow using `React.autoBind` on internal methods.
if (specPolicy != null) {
invariant(
!property || !property.__reactAutoBind,
'ReactCompositeComponentInterface: You are attempting to use ' +
'`React.autoBind` on `%s`, a method that is internal to React.' +
'Internal methods are called with the component as the context.',
name
);
}
// Disallow defining methods more than once unless explicitly allowed.
if (proto.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.DEFINE_MANY,
'ReactCompositeComponentInterface: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be due ' +
'to a mixin.',
name
);
}
validateMethodOverride(proto, name);
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else if (property && property.__reactAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property.__reactAutoBind;
} else if (proto.hasOwnProperty(name)) {
// For methods which are defined more than once, call the existing methods
// before calling the new property.
proto[name] = createChainedFunction(proto[name], property);
} else {
proto[name] = property;
// Setup methods on prototype:
// The following member methods should not be automatically bound:
// 1. Expected ReactCompositeComponent methods (in the "interface").
// 2. Overridden methods (that were mixed in).
var isCompositeComponentMethod = name in ReactCompositeComponentInterface;
var isInherited = name in proto;
var markedDontBind = property.__reactDontBind;
var isFunction = typeof property === 'function';
var shouldAutoBind =
isFunction &&
!isCompositeComponentMethod &&
!isInherited &&
!markedDontBind;
if (shouldAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property;
proto[name] = property;
} else {
if (isInherited) {
// For methods which are defined more than once, call the existing
// methods before calling the new property.
proto[name] = createChainedFunction(proto[name], property);
} else {
proto[name] = property;
}
}
}
}
}
@@ -342,13 +383,9 @@ function mixSpecIntoComponent(Constructor, spec) {
* @private
*/
function createChainedFunction(one, two) {
return function chainedFunction(a, b, c, d, e, tooMany) {
invariant(
typeof tooMany === 'undefined',
'Chained function can only take a maximum of 5 arguments.'
);
one.call(this, a, b, c, d, e);
two.call(this, a, b, c, d, e);
return function chainedFunction() {
one.apply(this, arguments);
two.apply(this, arguments);
};
}
@@ -357,7 +394,26 @@ function createChainedFunction(one, two) {
* `this._compositeLifeCycleState` (which can be null).
*
* This is different from the life cycle state maintained by `ReactComponent` in
* `this._lifeCycleState`.
* `this._lifeCycleState`. The following diagram shows how the states overlap in
* time. There are times when the CompositeLifeCycle is null - at those times it
* is only meaningful to look at ComponentLifeCycle alone.
*
* Top Row: ReactComponent.ComponentLifeCycle
* Low Row: ReactComponent.CompositeLifeCycle
*
* +-------+------------------------------------------------------+--------+
* | UN | MOUNTED | UN |
* |MOUNTED| | MOUNTED|
* +-------+------------------------------------------------------+--------+
* | ^--------+ +------+ +------+ +------+ +--------^ |
* | | | | | | | | | | | |
* | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 |
* | | | |PROPS | | PROPS| | STATE| |MOUNTING| |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +--------+ +------+ +------+ +------+ +--------+ |
* | | | |
* +-------+------------------------------------------------------+--------+
*/
var CompositeLifeCycle = keyMirror({
/**
@@ -396,12 +452,24 @@ var ReactCompositeComponentMixin = {
* @internal
*/
construct: function(initialProps, children) {
ReactComponent.Mixin.construct.call(this, initialProps, children);
// Children can be either an array or more than one argument
ReactComponent.Mixin.construct.apply(this, arguments);
this.state = null;
this._pendingState = null;
this._compositeLifeCycleState = null;
},
/**
* Checks whether or not this composite component is mounted.
* @return {boolean} True if mounted, false otherwise.
* @protected
* @final
*/
isMounted: function() {
return ReactComponent.Mixin.isMounted.call(this) &&
this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
},
/**
* Initializes the component, renders markup, and registers event listeners.
*
@@ -413,14 +481,10 @@ var ReactCompositeComponentMixin = {
*/
mountComponent: function(rootID, transaction) {
ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
// Unset `this._lifeCycleState` until after this method is finished.
this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
if (this.constructor.propDeclarations) {
this._assertValidProps(this.props);
}
this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
this._processProps(this.props);
if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
@@ -428,6 +492,7 @@ var ReactCompositeComponentMixin = {
this.state = this.getInitialState ? this.getInitialState() : null;
this._pendingState = null;
this._pendingForceUpdate = false;
if (this.componentWillMount) {
this.componentWillMount();
@@ -439,17 +504,15 @@ var ReactCompositeComponentMixin = {
}
}
if (this.componentDidMount) {
transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
}
this._renderedComponent = this._renderValidatedComponent();
// Done with mounting, `setState` will now trigger UI changes.
this._compositeLifeCycleState = null;
this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
return this._renderedComponent.mountComponent(rootID, transaction);
var markup = this._renderedComponent.mountComponent(rootID, transaction);
if (this.componentDidMount) {
transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
}
return markup;
},
/**
@@ -465,6 +528,8 @@ var ReactCompositeComponentMixin = {
}
this._compositeLifeCycleState = null;
this._defaultProps = null;
ReactComponent.Mixin.unmountComponent.call(this);
this._renderedComponent.unmountComponent();
this._renderedComponent = null;
@@ -479,33 +544,6 @@ var ReactCompositeComponentMixin = {
// TODO: this.state = null;
},
/**
* Updates the rendered DOM nodes given a new set of props.
*
* @param {object} nextProps Next set of properties.
* @param {ReactReconcileTransaction} transaction
* @final
* @internal
*/
receiveProps: function(nextProps, transaction) {
if (this.constructor.propDeclarations) {
this._assertValidProps(nextProps);
}
ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
if (this.componentWillReceiveProps) {
this.componentWillReceiveProps(nextProps, transaction);
}
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
// When receiving props, calls to `setState` by `componentWillReceiveProps`
// will set `this._pendingState` without triggering a re-render.
var nextState = this._pendingState || this.state;
this._pendingState = null;
this._receivePropsAndState(nextProps, nextState, transaction);
this._compositeLifeCycleState = null;
},
/**
* Sets a subset of the state. Always use this or `replaceState` to mutate
* state. You should treat `this.state` as immutable.
@@ -513,13 +551,22 @@ var ReactCompositeComponentMixin = {
* There is no guarantee that `this.state` will be immediately updated, so
* accessing `this.state` after calling this method may return the old value.
*
* There is no guarantee that calls to `setState` will run synchronously,
* as they may eventually be batched together. You can provide an optional
* callback that will be executed when the call to setState is actually
* completed.
*
* @param {object} partialState Next partial state to be merged with state.
* @param {?function} callback Called after state is updated.
* @final
* @protected
*/
setState: function(partialState) {
setState: function(partialState, callback) {
// Merge with `_pendingState` if it exists, otherwise with existing state.
this.replaceState(merge(this._pendingState || this.state, partialState));
this.replaceState(
merge(this._pendingState || this.state, partialState),
callback
);
},
/**
@@ -530,60 +577,89 @@ var ReactCompositeComponentMixin = {
* accessing `this.state` after calling this method may return the old value.
*
* @param {object} completeState Next state.
* @param {?function} callback Called after state is updated.
* @final
* @protected
*/
replaceState: function(completeState) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted (or mounting) component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
replaceState: function(completeState, callback) {
validateLifeCycleOnReplaceState(this);
this._pendingState = completeState;
// Do not trigger a state transition if we are in the middle of mounting or
// receiving props because both of those will already be doing this.
if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState;
this._pendingState = null;
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._receivePropsAndState,
this,
this.props,
nextState,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._compositeLifeCycleState = null;
}
ReactUpdates.enqueueUpdate(this, callback);
},
/**
* Receives next props and next state, and negotiates whether or not the
* component should update as a result.
* Processes props by setting default values for unspecified props and
* asserting that the props are valid.
*
* @param {object} nextProps Next object to set as props.
* @param {?object} nextState Next object to set as state.
* @param {ReactReconcileTransaction} transaction
* @param {object} props
* @private
*/
_receivePropsAndState: function(nextProps, nextState, transaction) {
if (!this.shouldComponentUpdate ||
_processProps: function(props) {
var propName;
var defaultProps = this._defaultProps;
for (propName in defaultProps) {
if (!(propName in props)) {
props[propName] = defaultProps[propName];
}
}
var propTypes = this.constructor.propTypes;
if (propTypes) {
var componentName = this.constructor.displayName;
for (propName in propTypes) {
var checkProp = propTypes[propName];
if (checkProp) {
checkProp(props, propName, componentName);
}
}
}
},
performUpdateIfNecessary: function() {
var compositeLifeCycleState = this._compositeLifeCycleState;
// Do not trigger a state transition if we are in the middle of mounting or
// receiving props because both of those will already be doing this.
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
return;
}
ReactComponent.Mixin.performUpdateIfNecessary.call(this);
},
/**
* If any of `_pendingProps`, `_pendingState`, or `_pendingForceUpdate` is
* set, update the component.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
_performUpdateIfNecessary: function(transaction) {
if (this._pendingProps == null &&
this._pendingState == null &&
!this._pendingForceUpdate) {
return;
}
var nextProps = this.props;
if (this._pendingProps != null) {
nextProps = this._pendingProps;
this._processProps(nextProps);
this._pendingProps = null;
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
if (this.componentWillReceiveProps) {
this.componentWillReceiveProps(nextProps, transaction);
}
}
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState || this.state;
this._pendingState = null;
if (this._pendingForceUpdate ||
!this.shouldComponentUpdate ||
this.shouldComponentUpdate(nextProps, nextState)) {
this._pendingForceUpdate = false;
// Will set `this.props` and `this.state`.
this._performComponentUpdate(nextProps, nextState, transaction);
} else {
@@ -592,6 +668,8 @@ var ReactCompositeComponentMixin = {
this.props = nextProps;
this.state = nextState;
}
this._compositeLifeCycleState = null;
},
/**
@@ -614,7 +692,7 @@ var ReactCompositeComponentMixin = {
this.props = nextProps;
this.state = nextState;
this.updateComponent(transaction);
this.updateComponent(transaction, prevProps, prevState);
if (this.componentDidUpdate) {
transaction.getReactOnDOMReady().enqueue(
@@ -631,16 +709,17 @@ var ReactCompositeComponentMixin = {
* Sophisticated clients may wish to override this.
*
* @param {ReactReconcileTransaction} transaction
* @param {object} prevProps
* @param {?object} prevState
* @internal
* @overridable
*/
updateComponent: function(transaction) {
updateComponent: function(transaction, prevProps, prevState) {
ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
var currentComponent = this._renderedComponent;
var nextComponent = this._renderValidatedComponent();
if (currentComponent.constructor === nextComponent.constructor) {
if (!nextComponent.props.isStatic) {
currentComponent.receiveProps(nextComponent.props, transaction);
}
currentComponent.receiveProps(nextComponent.props, transaction);
} else {
// These two IDs are actually the same! But nothing should rely on that.
var thisID = this._rootNodeID;
@@ -665,28 +744,42 @@ var ReactCompositeComponentMixin = {
* This will not invoke `shouldUpdateComponent`, but it will invoke
* `componentWillUpdate` and `componentDidUpdate`.
*
* @param {?function} callback Called after update is complete.
* @final
* @protected
*/
forceUpdate: function() {
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._performComponentUpdate,
this,
this.props,
this.state,
transaction
forceUpdate: function(callback) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this.isMounted() ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'forceUpdate(...): Can only force an update on mounted or mounting ' +
'components.'
);
ReactComponent.ReactReconcileTransaction.release(transaction);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'forceUpdate(...): Cannot force an update while unmounting component ' +
'or during an existing state transition (such as within `render`).'
);
this._pendingForceUpdate = true;
ReactUpdates.enqueueUpdate(this, callback);
},
/**
* @private
*/
_renderValidatedComponent: function() {
var renderedComponent;
ReactCurrentOwner.current = this;
var renderedComponent = this.render();
ReactCurrentOwner.current = null;
try {
renderedComponent = this.render();
} catch (error) {
// IE8 requires `catch` in order to use `finally`.
throw error;
} finally {
ReactCurrentOwner.current = null;
}
invariant(
ReactComponent.isValidComponent(renderedComponent),
'%s.render(): A valid ReactComponent must be returned.',
@@ -695,21 +788,6 @@ var ReactCompositeComponentMixin = {
return renderedComponent;
},
/**
* @param {object} props
* @private
*/
_assertValidProps: function(props) {
var propDeclarations = this.constructor.propDeclarations;
var componentName = this.constructor.displayName;
for (var propName in propDeclarations) {
var checkProp = propDeclarations[propName];
if (checkProp) {
checkProp(props, propName, componentName);
}
}
},
/**
* @private
*/
@@ -731,29 +809,34 @@ var ReactCompositeComponentMixin = {
*/
_bindAutoBindMethod: function(method) {
var component = this;
var hasWarned = false;
function autoBound(a, b, c, d, e, tooMany) {
invariant(
typeof tooMany === 'undefined',
'React.autoBind(...): Methods can only take a maximum of 5 arguments.'
);
if (component._lifeCycleState === ReactComponent.LifeCycle.MOUNTED) {
return method.call(component, a, b, c, d, e);
} else if (!hasWarned) {
hasWarned = true;
if (__DEV__) {
var boundMethod = function() {
return method.apply(component, arguments);
};
if (__DEV__) {
var componentName = component.constructor.displayName;
var _bind = boundMethod.bind;
boundMethod.bind = function(newThis) {
// User is trying to bind() an autobound method; we effectively will
// ignore the value of "this" that the user is trying to use, so
// let's warn.
if (newThis !== component) {
console.warn(
'React.autoBind(...): Attempted to invoke an auto-bound method ' +
'on an unmounted instance of `%s`. You either have a memory leak ' +
'or an event handler that is being run after unmounting.',
component.constructor.displayName || 'ReactCompositeComponent'
'bind(): React component methods may only be bound to the ' +
'component instance. See ' + componentName
);
} else if (arguments.length === 1) {
console.warn(
'bind(): You are binding a component method to the component. ' +
'React does this for you automatically in a high-performance ' +
'way, so you can safely remove this call. See ' + componentName
);
return boundMethod;
}
}
return _bind.apply(boundMethod, arguments);
};
}
return autoBound;
return boundMethod;
}
};
var ReactCompositeComponentBase = function() {};
@@ -784,9 +867,7 @@ var ReactCompositeComponent = {
* @public
*/
createClass: function(spec) {
var Constructor = function(initialProps, children) {
this.construct(initialProps, children);
};
var Constructor = function() {};
Constructor.prototype = new ReactCompositeComponentBase();
Constructor.prototype.constructor = Constructor;
mixSpecIntoComponent(Constructor, spec);
@@ -794,9 +875,17 @@ var ReactCompositeComponent = {
Constructor.prototype.render,
'createClass(...): Class specification must implement a `render` method.'
);
// Reduce time spent doing lookups by setting these on the prototype.
for (var methodName in ReactCompositeComponentInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
var ConvenienceConstructor = function(props, children) {
return new Constructor(props, children);
var instance = new Constructor();
instance.construct.apply(instance, arguments);
return instance;
};
ConvenienceConstructor.componentConstructor = Constructor;
ConvenienceConstructor.originalSpec = spec;
@@ -804,33 +893,23 @@ var ReactCompositeComponent = {
},
/**
* Marks the provided method to be automatically bound to the component.
* This means the method's context will always be the component.
*
* React.createClass({
* handleClick: React.autoBind(function() {
* this.setState({jumping: true});
* }),
* render: function() {
* return <a onClick={this.handleClick}>Jump</a>;
* }
* });
* TODO: Delete this when all callers have been updated to rely on this
* behavior being the default.
*
* Backwards compatible stub for what is now the default behavior.
* @param {function} method Method to be bound.
* @public
*/
autoBind: function(method) {
function unbound() {
invariant(
false,
'React.autoBind(...): Attempted to invoke an auto-bound method that ' +
'was not correctly defined on the class specification.'
if (__DEV__) {
console.warn(
'React.autoBind() is now deprecated. All React component methods ' +
'are auto bound by default, so React.autoBind() is a no-op. It ' +
'will be removed in the next version of React'
);
}
unbound.__reactAutoBind = method;
return unbound;
return method;
}
};
module.exports = ReactCompositeComponent;

View File

@@ -23,6 +23,8 @@
*
* The current owner is the component who should own any components that are
* currently being constructed.
*
* The depth indicate how many composite components are above this render level.
*/
var ReactCurrentOwner = {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule ReactDOM
* @typechecks
* @typechecks static-only
*/
"use strict";
@@ -40,16 +40,17 @@ var objMapKeyVal = require('objMapKeyVal');
* @private
*/
function createDOMComponentClass(tag, omitClose) {
var Constructor = function(initialProps, children) {
this.construct(initialProps, children);
};
var Constructor = function() {};
Constructor.prototype = new ReactNativeComponent(tag, omitClose);
Constructor.prototype.constructor = Constructor;
return function(props, children) {
return new Constructor(props, children);
var ConvenienceConstructor = function(props, children) {
var instance = new Constructor();
instance.construct.apply(instance, arguments);
return instance;
};
ConvenienceConstructor.componentConstructor = Constructor;
return ConvenienceConstructor;
}
/**
@@ -62,74 +63,118 @@ var ReactDOM = objMapKeyVal({
a: false,
abbr: false,
address: false,
area: false,
article: false,
aside: false,
audio: false,
b: false,
base: false,
bdi: false,
bdo: false,
big: false,
blockquote: false,
body: false,
br: true,
button: false,
canvas: false,
caption: false,
cite: false,
code: false,
col: true,
colgroup: false,
data: false,
datalist: false,
dd: false,
del: false,
details: false,
dfn: false,
div: false,
section: false,
dl: false,
dt: false,
em: false,
embed: true,
fieldset: false,
figcaption: false,
figure: false,
footer: false,
// Danger: this gets monkeypatched! See ReactDOMForm for more info.
form: false,
form: false, // NOTE: Injected, see `ReactDOMForm`.
h1: false,
h2: false,
h3: false,
h4: false,
h5: false,
h6: false,
head: false,
header: false,
hr: true,
html: false,
i: false,
iframe: false,
img: true,
input: true,
ins: false,
kbd: false,
keygen: true,
label: false,
legend: false,
li: false,
line: false,
link: false,
main: false,
map: false,
mark: false,
menu: false,
menuitem: false, // NOTE: Close tag should be omitted, but causes problems.
meta: true,
meter: false,
nav: false,
noscript: false,
object: false,
ol: false,
optgroup: false,
option: false,
output: false,
p: false,
param: true,
pre: false,
progress: false,
q: false,
rp: false,
rt: false,
ruby: false,
s: false,
samp: false,
script: false,
section: false,
select: false,
small: false,
source: false,
span: false,
sub: false,
sup: false,
strong: false,
style: false,
sub: false,
summary: false,
sup: false,
table: false,
tbody: false,
td: false,
textarea: false,
textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
tfoot: false,
th: false,
thead: false,
time: false,
title: false,
tr: false,
track: true,
u: false,
ul: false,
'var': false,
video: false,
wbr: false,
// SVG
circle: false,
g: false,
line: false,
path: false,
polyline: false,
rect: false,

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule ReactDOMIDOperations
* @typechecks
* @typechecks static-only
*/
/*jslint evil: true */
@@ -24,7 +24,7 @@
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMChildrenOperations = require('DOMChildrenOperations');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactDOMNodeCache = require('ReactDOMNodeCache');
var ReactID = require('ReactID');
var getTextContentAccessor = require('getTextContentAccessor');
var invariant = require('invariant');
@@ -36,7 +36,6 @@ var invariant = require('invariant');
* @private
*/
var INVALID_PROPERTY_ERRORS = {
content: '`content` must be set using `updateTextContentByID()`.',
dangerouslySetInnerHTML:
'`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
style: '`style` must be set using `updateStylesByID()`.'
@@ -66,7 +65,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updatePropertyByID: function(id, name, value) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
invariant(
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
'updatePropertyByID(...): %s',
@@ -75,6 +74,24 @@ var ReactDOMIDOperations = {
DOMPropertyOperations.setValueForProperty(node, name, value);
},
/**
* Updates a DOM node to remove a property. This should only be used to remove
* DOM properties in `DOMProperty`.
*
* @param {string} id ID of the node to update.
* @param {string} name A property name to remove, see `DOMProperty`.
* @internal
*/
deletePropertyByID: function(id, name, value) {
var node = ReactID.getNode(id);
invariant(
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
'updatePropertyByID(...): %s',
INVALID_PROPERTY_ERRORS[name]
);
DOMPropertyOperations.deleteValueForProperty(node, name, value);
},
/**
* This should almost never be used instead of `updatePropertyByID()` due to
* the extra object allocation required by the API. That said, this is useful
@@ -95,14 +112,15 @@ var ReactDOMIDOperations = {
},
/**
* Updates a DOM node with new style values.
* Updates a DOM node with new style values. If a value is specified as '',
* the corresponding style property will be unset.
*
* @param {string} id ID of the node to update.
* @param {object} styles Mapping from styles to values.
* @internal
*/
updateStylesByID: function(id, styles) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
CSSPropertyOperations.setValueForStyles(node, styles);
},
@@ -114,7 +132,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updateInnerHTMLByID: function(id, html) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
// HACK: IE8- normalize whitespace in innerHTML, removing leading spaces.
// @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
node.innerHTML = (html && html.__html || '').replace(/^ /g, '&nbsp;');
@@ -128,7 +146,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updateTextContentByID: function(id, content) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
node[textContentAccessor] = content;
},
@@ -141,9 +159,8 @@ var ReactDOMIDOperations = {
* @see {Danger.dangerouslyReplaceNodeWithMarkup}
*/
dangerouslyReplaceNodeWithMarkupByID: function(id, markup) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
ReactDOMNodeCache.purgeEntireCache();
},
/**
@@ -151,14 +168,8 @@ var ReactDOMIDOperations = {
* Detect if any elements were removed instead of blindly purging.
*/
manageChildrenByParentID: function(parentID, domOperations) {
var parent = ReactDOMNodeCache.getCachedNodeByID(parentID);
var parent = ReactID.getNode(parentID);
DOMChildrenOperations.manageChildren(parent, domOperations);
ReactDOMNodeCache.purgeEntireCache();
},
setTextNodeValueAtIndexByParentID: function(parentID, index, value) {
var parent = ReactDOMNodeCache.getCachedNodeByID(parentID);
DOMChildrenOperations.setTextNodeValueAtIndex(parent, index, value);
}
};

View File

@@ -1,52 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDOMNodeCache
*/
"use strict";
var ReactMount = require('ReactMount');
var nodeCache = {};
/**
* DOM node cache only intended for use by React. Placed into a shared module so
* that both read and write utilities may benefit from a shared cache.
*/
var ReactDOMNodeCache = {
/**
* Releases fast id lookups (node/style cache). This implementation is
* aggressive with purging because the bookkeeping associated with doing fine
* grained deleted from the cache may outweight the benefits of the cache. The
* heuristic that should be used to purge is 'any time anything is deleted'.
* Typically this means that a large amount of content is being replaced and
* several elements would need purging regardless. It's also a time when an
* application is likely not in the middle of a "smooth operation" (such as
* animating/scrolling).
*/
purgeEntireCache: function() {
nodeCache = {};
return nodeCache;
},
getCachedNodeByID: function(id) {
return nodeCache[id] ||
(nodeCache[id] =
document.getElementById(id) ||
ReactMount.findReactRenderedDOMNodeSlow(id));
}
};
module.exports = ReactDOMNodeCache;

View File

@@ -20,9 +20,17 @@
var ReactDOM = require('ReactDOM');
var ReactDOMForm = require('ReactDOMForm');
var ReactDOMInput = require('ReactDOMInput');
var ReactDOMOption = require('ReactDOMOption');
var ReactDOMSelect = require('ReactDOMSelect');
var ReactDOMTextarea = require('ReactDOMTextarea');
var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
var DOMProperty = require('DOMProperty');
var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var ChangeEventPlugin = require('ChangeEventPlugin');
var EventPluginHub = require('EventPluginHub');
var ReactInstanceHandles = require('ReactInstanceHandles');
var SimpleEventPlugin = require('SimpleEventPlugin');
@@ -35,23 +43,24 @@ function inject() {
EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
/**
* Two important event plugins included by default (without having to require
* Some important event plugins included by default (without having to require
* them).
*/
EventPluginHub.injection.injectEventPluginsByName({
'SimpleEventPlugin': SimpleEventPlugin,
'EnterLeaveEventPlugin': EnterLeaveEventPlugin
'EnterLeaveEventPlugin': EnterLeaveEventPlugin,
'ChangeEventPlugin': ChangeEventPlugin
});
/*
* This is a bit of a hack. We need to override the <form> element
* to be a composite component because IE8 does not bubble or capture
* submit to the top level. In order to make this work with our
* dependency graph we need to inject it here.
*/
ReactDOM.injection.injectComponentClasses({
form: ReactDOMForm
form: ReactDOMForm,
input: ReactDOMInput,
option: ReactDOMOption,
select: ReactDOMSelect,
textarea: ReactDOMTextarea
});
DOMProperty.injection.injectDOMPropertyConfig(DefaultDOMPropertyConfig);
}
module.exports = {

View File

@@ -0,0 +1,50 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDoNotBindDeprecated
*/
"use strict";
var ReactDoNotBindDeprecated = {
/**
* Marks the method for not being automatically bound on component mounting. A
* couple of reasons you might want to use this:
*
* - Automatically supporting the previous behavior in components that were
* built with previous versions of React.
* - Tuning performance, by avoiding binding on initial render for methods
* that are always invoked while being preceded by `this.`. Such binds are
* unnecessary.
*
* React.createClass({
* handleClick: ReactDoNotBindDeprecated.doNotBind(function() {
* alert(this.setState); // undefined!
* }),
* render: function() {
* return <a onClick={this.handleClick}>Jump</a>;
* }
* });
*
* @param {function} method Method to avoid automatically binding.
* @public
*/
doNotBind: function(method) {
method.__reactDontBind = true; // Mutating
return method;
}
};
module.exports = ReactDoNotBindDeprecated;

View File

@@ -1,321 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactEvent
*/
"use strict";
var BrowserEnv = require('BrowserEnv');
var EventConstants = require('EventConstants');
var EventPluginHub = require('EventPluginHub');
var ExecutionEnvironment = require('ExecutionEnvironment');
var NormalizedEventListener = require('NormalizedEventListener');
var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
var registrationNames = EventPluginHub.registrationNames;
var topLevelTypes = EventConstants.topLevelTypes;
var listen = NormalizedEventListener.listen;
var capture = NormalizedEventListener.capture;
/**
* `ReactEvent` is used to attach top-level event listeners. For example:
*
* ReactEvent.putListener('myID', 'onClick', myFunction);
*
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
*/
/**
* Overview of React and the event system:
*
* .
* +-------------+ .
* | DOM | .
* +-------------+ . +-----------+
* + . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|-------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +------------.---->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | | . | |<---+|Plugin | |other plugin|
* | +------------.---------+ | +-----------+ | utilities |
* | | | . | | | +------------+
* | | | . +---|----------+
* | | | . | ^ +-----------+
* | | | . | | |Enter/Leave|
* +-----| ------+ . | +-------+|Plugin |
* | . v +-----------+
* + . +--------+
* +-------------+ . |callback|
* | application | . |registry|
* |-------------| . +--------+
* | | .
* | | .
* | | .
* | | .
* +-------------+ .
* .
* React Core . General Purpose Event Plugin System
*/
/**
* We listen for bubbled touch events on the document object.
*
* Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
* `onmousemove` events at some node that was not the document element. The
* symptoms were that if your mouse is not moving over something contained
* within that mount point (for example on the background) the top-level
* listeners for `onmousemove` won't be called. However, if you register the
* `mousemove` on the document object, then it will of course catch all
* `mousemove`s. This along with iOS quirks, justifies restricting top-level
* listeners to the document object only, at least for these movement types of
* events and possibly all events.
*
* @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
*
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
* they bubble to document.
*
* @see http://www.quirksmode.org/dom/events/keys.html.
*/
var _isListening = false;
var EVENT_LISTEN_MISUSE;
var WORKER_DISABLE;
if (__DEV__) {
EVENT_LISTEN_MISUSE =
'You must register listeners at the top of the document, only once - ' +
'and only in the main UI thread of a browser - if you are attempting ' +
'listen in a worker, the framework is probably doing something wrong ' +
'and you should report this immediately.';
WORKER_DISABLE =
'Cannot disable event listening in Worker thread. This is likely a ' +
'bug in the framework. Please report immediately.';
}
/**
* Traps top-level events that bubble. Delegates to the main dispatcher
* `handleTopLevel` after performing some basic normalization via
* `TopLevelCallbackCreator.createTopLevelCallback`.
*/
function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
listen(
onWhat,
handlerBaseName,
ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
);
}
/**
* Traps a top-level event by using event capturing.
*/
function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
capture(
onWhat,
handlerBaseName,
ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
);
}
/**
* Listens to document scroll and window resize events that may change the
* document scroll values. We store those results so as to discourage
* application code from asking the DOM itself which could trigger additional
* reflows.
*/
function registerDocumentScrollListener() {
listen(window, 'scroll', function(nativeEvent) {
if (nativeEvent.target === window) {
BrowserEnv.refreshAuthoritativeScrollValues();
}
});
}
function registerDocumentResizeListener() {
listen(window, 'resize', function(nativeEvent) {
if (nativeEvent.target === window) {
BrowserEnv.refreshAuthoritativeScrollValues();
}
});
}
/**
* Summary of `ReactEvent` event handling:
*
* - We trap low level 'top-level' events.
*
* - We dedupe cross-browser event names into these 'top-level types' so that
* `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
*
* - At this point we have native browser events with the top-level type that
* was used to catch it at the top-level.
*
* - We continuously stream these native events (and their respective top-level
* types) to the event plugin system `EventPluginHub` and ask the plugin
* system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
* objects are the events that applications actually deal with - they are not
* native browser events but cross-browser wrappers.
*
* - When returning the `AbstractEvent` objects, `EventPluginHub` will make
* sure each abstract event is annotated with "dispatches", which are the
* sequence of listeners (and IDs) that care about the event.
*
* - These `AbstractEvent` objects are fed back into the event plugin system,
* which in turn executes these dispatches.
*
* @private
*/
function listenAtTopLevel(touchNotMouse) {
invariant(
!_isListening,
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
);
var mountAt = document;
registerDocumentScrollListener();
registerDocumentResizeListener();
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
trapBubbledEvent(topLevelTypes.topMouseWheel, 'mousewheel', mountAt);
if (touchNotMouse) {
trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
}
trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
trapBubbledEvent(
topLevelTypes.topDOMCharacterDataModified,
'DOMCharacterDataModified',
mountAt
);
// Firefox needs to capture a different mouse scroll event.
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
trapBubbledEvent(topLevelTypes.topMouseWheel, 'DOMMouseScroll', mountAt);
// IE < 9 doesn't support capturing so just trap the bubbled event there.
if (isEventSupported('scroll', true)) {
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
} else {
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
}
if (isEventSupported('focus', true)) {
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
}
}
/**
* This is the heart of `ReactEvent`. It simply streams the top-level native
* events to `EventPluginHub`.
*
* @param {object} topLevelType Record from `EventConstants`.
* @param {Event} nativeEvent A Standard Event with fixed `target` property.
* @param {DOMElement} renderedTarget Element of interest to the framework.
* @param {string} renderedTargetID string ID of `renderedTarget`.
* @internal
*/
function handleTopLevel(
topLevelType,
nativeEvent,
renderedTargetID,
renderedTarget) {
var abstractEvents = EventPluginHub.extractAbstractEvents(
topLevelType,
nativeEvent,
renderedTargetID,
renderedTarget
);
// The event queue being processed in the same cycle allows preventDefault.
EventPluginHub.enqueueAbstractEvents(abstractEvents);
EventPluginHub.processAbstractEventQueue();
}
function setEnabled(enabled) {
invariant(
ExecutionEnvironment.canUseDOM,
'setEnabled(...): Cannot toggle event listening in a Worker thread. This ' +
'is likely a bug in the framework. Please report immediately.'
);
ReactEvent.TopLevelCallbackCreator.setEnabled(enabled);
}
function isEnabled() {
return ReactEvent.TopLevelCallbackCreator.isEnabled();
}
/**
* Ensures that top-level event delegation listeners are listening at `mountAt`.
* There are issues with listening to both touch events and mouse events on the
* top-level, so we make the caller choose which one to listen to. (If there's a
* touch top-level listeners, anchors don't receive clicks for some reason, and
* only in some cases).
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @param {object} TopLevelCallbackCreator Module that can create top-level
* callback handlers.
* @internal
*/
function ensureListening(touchNotMouse, TopLevelCallbackCreator) {
invariant(
ExecutionEnvironment.canUseDOM,
'ensureListening(...): Cannot toggle event listening in a Worker thread. ' +
'This is likely a bug in the framework. Please report immediately.'
);
if (!_isListening) {
ReactEvent.TopLevelCallbackCreator = TopLevelCallbackCreator;
listenAtTopLevel(touchNotMouse);
_isListening = true;
}
}
var ReactEvent = {
TopLevelCallbackCreator: null, // Injectable callback creator.
handleTopLevel: handleTopLevel,
setEnabled: setEnabled,
isEnabled: isEnabled,
ensureListening: ensureListening,
registrationNames: registrationNames,
putListener: EventPluginHub.putListener,
getListener: EventPluginHub.getListener,
deleteAllListeners: EventPluginHub.deleteAllListeners,
trapBubbledEvent: trapBubbledEvent,
trapCapturedEvent: trapCapturedEvent
};
module.exports = ReactEvent;

View File

@@ -0,0 +1,346 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactEventEmitter
* @typechecks static-only
*/
"use strict";
var EventConstants = require('EventConstants');
var EventListener = require('EventListener');
var EventPluginHub = require('EventPluginHub');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactUpdates = require('ReactUpdates');
var ViewportMetrics = require('ViewportMetrics');
var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
/**
* Summary of `ReactEventEmitter` event handling:
*
* - Top-level delegation is used to trap native browser events. We normalize
* and de-duplicate events to account for browser quirks.
*
* - Forward these native events (with the associated top-level type used to
* trap it) to `EventPluginHub`, which in turn will ask plugins if they want
* to extract any synthetic events.
*
* - The `EventPluginHub` will then process each event by annotating them with
* "dispatches", a sequence of listeners and IDs that care about that event.
*
* - The `EventPluginHub` then dispatches the events.
*
* Overview of React and the event system:
*
* .
* +------------+ .
* | DOM | .
* +------------+ . +-----------+
* + . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.---------+ | +------------+
* | | | . +----|---------+
* +-----|------+ . | ^ +-----------+
* | . | | |Enter/Leave|
* + . | +-------+|Plugin |
* +-------------+ . v +-----------+
* | application | . +----------+
* |-------------| . | callback |
* | | . | registry |
* | | . +----------+
* +-------------+ .
* .
* React Core . General Purpose Event Plugin System
*/
/**
* Whether or not `ensureListening` has been invoked.
* @type {boolean}
* @private
*/
var _isListening = false;
/**
* Traps top-level events by using event bubbling.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} handlerBaseName Event name (e.g. "click").
* @param {DOMEventTarget} element Element on which to attach listener.
* @internal
*/
function trapBubbledEvent(topLevelType, handlerBaseName, element) {
EventListener.listen(
element,
handlerBaseName,
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
topLevelType
)
);
}
/**
* Traps a top-level event by using event capturing.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} handlerBaseName Event name (e.g. "click").
* @param {DOMEventTarget} element Element on which to attach listener.
* @internal
*/
function trapCapturedEvent(topLevelType, handlerBaseName, element) {
EventListener.capture(
element,
handlerBaseName,
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
topLevelType
)
);
}
/**
* Listens to window scroll and resize events. We cache scroll values so that
* application code can access them without triggering reflows.
*
* NOTE: Scroll events do not bubble.
*
* @private
* @see http://www.quirksmode.org/dom/events/scroll.html
*/
function registerScrollValueMonitoring() {
var refresh = ViewportMetrics.refreshScrollValues;
EventListener.listen(window, 'scroll', refresh);
EventListener.listen(window, 'resize', refresh);
}
/**
* We listen for bubbled touch events on the document object.
*
* Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
* `onmousemove` events at some node that was not the document element. The
* symptoms were that if your mouse is not moving over something contained
* within that mount point (for example on the background) the top-level
* listeners for `onmousemove` won't be called. However, if you register the
* `mousemove` on the document object, then it will of course catch all
* `mousemove`s. This along with iOS quirks, justifies restricting top-level
* listeners to the document object only, at least for these movement types of
* events and possibly all events.
*
* @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
*
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
* they bubble to document.
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @private
* @see http://www.quirksmode.org/dom/events/keys.html.
*/
function listenAtTopLevel(touchNotMouse) {
invariant(
!_isListening,
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
);
var topLevelTypes = EventConstants.topLevelTypes;
var mountAt = document;
registerScrollValueMonitoring();
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
if (touchNotMouse) {
trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
}
trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
trapBubbledEvent(
topLevelTypes.topSelectionChange,
'selectionchange',
mountAt
);
trapBubbledEvent(
topLevelTypes.topDOMCharacterDataModified,
'DOMCharacterDataModified',
mountAt
);
if (isEventSupported('drag')) {
trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
}
if (isEventSupported('wheel')) {
trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
} else if (isEventSupported('mousewheel')) {
trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
} else {
// Firefox needs to capture a different mouse scroll event.
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
}
// IE<9 does not support capturing so just trap the bubbled event there.
if (isEventSupported('scroll', true)) {
trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
} else {
trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
}
if (isEventSupported('focus', true)) {
trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
}
}
/**
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
*
* ReactEventEmitter.putListener('myID', 'onClick', myFunction);
*
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
*
* @internal
*/
var ReactEventEmitter = {
/**
* React references `ReactEventTopLevelCallback` using this property in order
* to allow dependency injection via `ensureListening`.
*/
TopLevelCallbackCreator: null,
/**
* Ensures that top-level event delegation listeners are installed.
*
* There are issues with listening to both touch events and mouse events on
* the top-level, so we make the caller choose which one to listen to. (If
* there's a touch top-level listeners, anchors don't receive clicks for some
* reason, and only in some cases).
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @param {object} TopLevelCallbackCreator
*/
ensureListening: function(touchNotMouse, TopLevelCallbackCreator) {
invariant(
ExecutionEnvironment.canUseDOM,
'ensureListening(...): Cannot toggle event listening in a Worker ' +
'thread. This is likely a bug in the framework. Please report ' +
'immediately.'
);
if (!_isListening) {
ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
listenAtTopLevel(touchNotMouse);
_isListening = true;
}
},
/**
* Sets whether or not any created callbacks should be enabled.
*
* @param {boolean} enabled True if callbacks should be enabled.
*/
setEnabled: function(enabled) {
invariant(
ExecutionEnvironment.canUseDOM,
'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
'This is likely a bug in the framework. Please report immediately.'
);
if (ReactEventEmitter.TopLevelCallbackCreator) {
ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
}
},
/**
* @return {boolean} True if callbacks are enabled.
*/
isEnabled: function() {
return !!(
ReactEventEmitter.TopLevelCallbackCreator &&
ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
);
},
/**
* Streams a fired top-level event to `EventPluginHub` where plugins have the
* opportunity to create `ReactEvent`s to be dispatched.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
*/
handleTopLevel: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var events = EventPluginHub.extractEvents(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent
);
// Event queue being processed in the same cycle allows `preventDefault`.
ReactUpdates.batchedUpdates(function() {
EventPluginHub.enqueueEvents(events);
EventPluginHub.processEventQueue();
});
},
registrationNames: EventPluginHub.registrationNames,
putListener: EventPluginHub.putListener,
getListener: EventPluginHub.getListener,
deleteListener: EventPluginHub.deleteListener,
deleteAllListeners: EventPluginHub.deleteAllListeners,
trapBubbledEvent: trapBubbledEvent,
trapCapturedEvent: trapCapturedEvent
};
module.exports = ReactEventEmitter;

View File

@@ -14,58 +14,75 @@
* limitations under the License.
*
* @providesModule ReactEventTopLevelCallback
* @typechecks static-only
*/
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactEvent = require('ReactEvent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactID = require('ReactID');
var ReactInstanceHandles = require('ReactInstanceHandles');
var getDOMNodeID = require('getDOMNodeID');
var getEventTarget = require('getEventTarget');
/**
* @type {boolean}
* @private
*/
var _topLevelListenersEnabled = true;
/**
* Top-level callback creator used to implement event handling using delegation.
* This is used via dependency injection in `ReactEventEmitter.ensureListening`.
*/
var ReactEventTopLevelCallback = {
/**
* @param {boolean} enabled Whether or not all callbacks that have ever been
* created with this module should be enabled.
* Sets whether or not any created callbacks should be enabled.
*
* @param {boolean} enabled True if callbacks should be enabled.
*/
setEnabled: function(enabled) {
_topLevelListenersEnabled = !!enabled;
},
/**
* @return {boolean} True if callbacks are enabled.
*/
isEnabled: function() {
return _topLevelListenersEnabled;
},
/**
* For a given `topLevelType`, creates a callback that could be added as a
* listener to the document. That top level callback will simply fix the
* native events before invoking `handleTopLevel`.
* Creates a callback for the supplied `topLevelType` that could be added as
* a listener to the document. The callback computes a `topLevelTarget` which
* should be the root node of a mounted React component where the listener
* is attached.
*
* - Raw native events cannot be trusted to describe their targets correctly
* so we expect that the argument to the nested function has already been
* fixed. But the `target` property may not be something of interest to
* React, so we find the most suitable target. But even at that point, DOM
* Elements (the target ) can't be trusted to describe their IDs correctly
* so we obtain the ID in a reliable manner and pass it to
* `handleTopLevel`. The target/id that we found to be relevant to our
* framework are called `renderedTarget`/`renderedTargetID` respectively.
* @param {string} topLevelType Record from `EventConstants`.
* @return {function} Callback for handling top-level events.
*/
createTopLevelCallback: function(topLevelType) {
return function(fixedNativeEvent) {
return function(nativeEvent) {
if (!_topLevelListenersEnabled) {
return;
}
var renderedTarget = ReactInstanceHandles.getFirstReactDOM(
fixedNativeEvent.target
// TODO: Remove when synthetic events are ready, this is for IE<9.
if (nativeEvent.srcElement &&
nativeEvent.srcElement !== nativeEvent.target) {
nativeEvent.target = nativeEvent.srcElement;
}
var topLevelTarget = ReactInstanceHandles.getFirstReactDOM(
getEventTarget(nativeEvent)
) || ExecutionEnvironment.global;
var renderedTargetID = getDOMNodeID(renderedTarget);
var event = fixedNativeEvent;
var target = renderedTarget;
ReactEvent.handleTopLevel(topLevelType, event, renderedTargetID, target);
var topLevelTargetID = ReactID.getID(topLevelTarget) || '';
ReactEventEmitter.handleTopLevel(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent
);
};
}

166
src/core/ReactID.js Normal file
View File

@@ -0,0 +1,166 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactID
* @typechecks static-only
*/
"use strict";
var invariant = require('invariant');
var ReactMount = require('ReactMount');
var ATTR_NAME = 'data-reactid';
var nodeCache = {};
/**
* Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
* element can return its control whose name or ID equals ATTR_NAME. All
* DOM nodes support `getAttributeNode` but this can also get called on
* other objects so just return '' if we're given something other than a
* DOM node (such as window).
*
* @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
* @return {string} ID of the supplied `domNode`.
*/
function getID(node) {
var id = internalGetID(node);
if (id) {
if (nodeCache.hasOwnProperty(id)) {
var cached = nodeCache[id];
if (cached !== node) {
invariant(
!isValid(cached, id),
'ReactID: Two valid but unequal nodes with the same `%s`: %s',
ATTR_NAME, id
);
nodeCache[id] = node;
}
} else {
nodeCache[id] = node;
}
}
return id;
}
function internalGetID(node) {
if (node && node.getAttributeNode) {
var attributeNode = node.getAttributeNode(ATTR_NAME);
if (attributeNode) {
return attributeNode.value || '';
}
}
return '';
}
/**
* Sets the React-specific ID of the given node.
*
* @param {DOMElement} node The DOM node whose ID will be set.
* @param {string} id The value of the ID attribute.
*/
function setID(node, id) {
var oldID = internalGetID(node);
if (oldID !== id) {
delete nodeCache[oldID];
}
node.setAttribute(ATTR_NAME, id);
nodeCache[id] = node;
}
/**
* Finds the node with the supplied React-generated DOM ID.
*
* @param {string} id A React-generated DOM ID.
* @return {DOMElement} DOM node with the suppled `id`.
* @internal
*/
function getNode(id) {
if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
nodeCache[id] = ReactMount.findReactNodeByID(id);
}
return nodeCache[id];
}
/**
* A node is "valid" if it is contained by a currently mounted container.
*
* This means that the node does not have to be contained by a document in
* order to be considered valid.
*
* @param {?DOMElement} node The candidate DOM node.
* @param {string} id The expected ID of the node.
* @return {boolean} Whether the node is contained by a mounted container.
*/
function isValid(node, id) {
if (node) {
invariant(
internalGetID(node) === id,
'ReactID: Unexpected modification of `%s`',
ATTR_NAME
);
var container = ReactMount.findReactContainerForID(id);
if (container && contains(container, node)) {
return true;
}
}
return false;
}
function contains(ancestor, descendant) {
if (ancestor.contains) {
// Supported natively in virtually all browsers, but not in jsdom.
return ancestor.contains(descendant);
}
if (descendant === ancestor) {
return true;
}
if (descendant.nodeType === 3) {
// If descendant is a text node, start from descendant.parentNode
// instead, so that we can assume all ancestors worth considering are
// element nodes with nodeType === 1.
descendant = descendant.parentNode;
}
while (descendant && descendant.nodeType === 1) {
if (descendant === ancestor) {
return true;
}
descendant = descendant.parentNode;
}
return false;
}
/**
* Causes the cache to forget about one React-specific ID.
*
* @param {string} id The ID to forget.
*/
function purgeID(id) {
delete nodeCache[id];
}
exports.ATTR_NAME = ATTR_NAME;
exports.getID = getID;
exports.rawGetID = internalGetID;
exports.setID = setID;
exports.getNode = getNode;
exports.purgeID = purgeID;

View File

@@ -27,6 +27,10 @@ function getActiveElement() {
}
}
function isInDocument(node) {
return document.documentElement.contains(node);
}
/**
* @ReactInputSelection: React input selection module. Based on Selection.js,
* but modified to be suitable for react and has a couple of bug fixes (doesn't
@@ -64,7 +68,7 @@ var ReactInputSelection = {
var priorFocusedElem = priorSelectionInformation.focusedElem;
var priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem &&
document.getElementById(priorFocusedElem.id)) {
isInDocument(priorFocusedElem)) {
if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
ReactInputSelection.setSelection(
priorFocusedElem,

View File

@@ -14,11 +14,13 @@
* limitations under the License.
*
* @providesModule ReactInstanceHandles
* @typechecks static-only
*/
"use strict";
var getDOMNodeID = require('getDOMNodeID');
var ReactID = require('ReactID');
var invariant = require('invariant');
var SEPARATOR = '.';
@@ -29,6 +31,26 @@ var SEPARATOR_LENGTH = SEPARATOR.length;
*/
var MAX_TREE_DEPTH = 100;
/**
* Size of the reactRoot ID space. We generate random numbers for React root
* IDs and if there's a collision the events and DOM update system will
* get confused. If we assume 100 React components per page, and a user
* loads 1 page per minute 24/7 for 50 years, with a mount point space of
* 9,999,999 the likelihood of never having a collision is 99.997%.
*/
var GLOBAL_MOUNT_POINT_MAX = 9999999;
/**
* Creates a DOM ID prefix to use when mounting React components.
*
* @param {number} index A unique integer
* @return {string} React root ID.
* @internal
*/
function getReactRootIDString(index) {
return SEPARATOR + 'r[' + index.toString(36) + ']';
}
/**
* Checks if a character in the supplied ID is a separator or the end.
*
@@ -37,7 +59,7 @@ var MAX_TREE_DEPTH = 100;
* @return {boolean} True if the character is a separator or end of the ID.
* @private
*/
function isMarker(id, index) {
function isBoundary(id, index) {
return id.charAt(index) === SEPARATOR || index === id.length;
}
@@ -55,15 +77,18 @@ function isValidID(id) {
}
/**
* True if the supplied `node` is rendered by React.
* Checks if the first ID is an ancestor of or equal to the second ID.
*
* @param {DOMElement} node DOM Element to check.
* @return {boolean} True if the DOM Element appears to be rendered by React.
* @private
* @param {string} ancestorID
* @param {string} descendantID
* @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
* @internal
*/
function isRenderedByReact(node) {
var id = getDOMNodeID(node);
return id && id.charAt(0) === SEPARATOR;
function isAncestorIDOf(ancestorID, descendantID) {
return (
descendantID.indexOf(ancestorID) === 0 &&
isBoundary(descendantID, ancestorID.length)
);
}
/**
@@ -73,10 +98,83 @@ function isRenderedByReact(node) {
* @return {string} ID of the parent, or an empty string.
* @private
*/
function parentID(id) {
function getParentID(id) {
return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
}
/**
* Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
* supplied `destinationID`. If they are equal, the ID is returned.
*
* @param {string} ancestorID ID of an ancestor node of `destinationID`.
* @param {string} destinationID ID of the destination node.
* @return {string} Next ID on the path from `ancestorID` to `destinationID`.
* @private
*/
function getNextDescendantID(ancestorID, destinationID) {
invariant(
isValidID(ancestorID) && isValidID(destinationID),
'getNextDescendantID(%s, %s): Received an invalid React DOM ID.',
ancestorID,
destinationID
);
invariant(
isAncestorIDOf(ancestorID, destinationID),
'getNextDescendantID(...): React has made an invalid assumption about ' +
'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
ancestorID,
destinationID
);
if (ancestorID === destinationID) {
return ancestorID;
}
// Skip over the ancestor and the immediate separator. Traverse until we hit
// another separator or we reach the end of `destinationID`.
var start = ancestorID.length + SEPARATOR_LENGTH;
for (var i = start; i < destinationID.length; i++) {
if (isBoundary(destinationID, i)) {
break;
}
}
return destinationID.substr(0, i);
}
/**
* Gets the nearest common ancestor ID of two IDs.
*
* Using this ID scheme, the nearest common ancestor ID is the longest common
* prefix of the two IDs that immediately preceded a "marker" in both strings.
*
* @param {string} oneID
* @param {string} twoID
* @return {string} Nearest common ancestor ID, or the empty string if none.
* @private
*/
function getFirstCommonAncestorID(oneID, twoID) {
var minLength = Math.min(oneID.length, twoID.length);
if (minLength === 0) {
return '';
}
var lastCommonMarkerIndex = 0;
// Use `<=` to traverse until the "EOL" of the shorter string.
for (var i = 0; i <= minLength; i++) {
if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
lastCommonMarkerIndex = i;
} else if (oneID.charAt(i) !== twoID.charAt(i)) {
break;
}
}
var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
invariant(
isValidID(longestCommonID),
'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
oneID,
twoID,
longestCommonID
);
return longestCommonID;
}
/**
* Traverses the parent path between two IDs (either up or down). The IDs must
* not be the same, and there must exist a parent path between them.
@@ -96,10 +194,9 @@ function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
start
);
var ancestorID = ReactInstanceHandles.getFirstCommonAncestorID(start, stop);
var traverseUp = ancestorID === stop;
var traverseUp = isAncestorIDOf(stop, start);
invariant(
traverseUp || ancestorID === start,
traverseUp || isAncestorIDOf(start, stop),
'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
'not have a parent path.',
start,
@@ -107,7 +204,7 @@ function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
);
// Traverse from `start` to `stop` one depth at a time.
var depth = 0;
var traverse = traverseUp ? parentID : ReactInstanceHandles.nextDescendantID;
var traverse = traverseUp ? getParentID : getNextDescendantID;
for (var id = start; /* until break */; id = traverse(id, stop)) {
if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
cb(id, traverseUp, arg);
@@ -136,18 +233,40 @@ var ReactInstanceHandles = {
separator: SEPARATOR,
createReactRootID: function() {
return getReactRootIDString(
Math.ceil(Math.random() * GLOBAL_MOUNT_POINT_MAX)
);
},
/**
* True if the supplied `node` is rendered by React.
*
* @param {*} node DOM Element to check.
* @return {boolean} True if the DOM Element appears to be rendered by React.
* @internal
*/
isRenderedByReact: function(node) {
if (node.nodeType !== 1) {
// Not a DOMElement, therefore not a React component
return false;
}
var id = ReactID.getID(node);
return id ? id.charAt(0) === SEPARATOR : false;
},
/**
* Traverses up the ancestors of the supplied node to find a node that is a
* DOM representation of a React component.
*
* @param {DOMElement} node
* @return {?DOMElement}
* @param {*} node
* @return {?DOMEventTarget}
* @internal
*/
getFirstReactDOM: function(node) {
var current = node;
while (current && current.parentNode !== current) {
if (isRenderedByReact(current)) {
if (ReactInstanceHandles.isRenderedByReact(current)) {
return current;
}
current = current.parentNode;
@@ -159,68 +278,62 @@ var ReactInstanceHandles = {
* Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
* Exploits the ID naming scheme to perform the search quickly.
*
* @param {DOMElement} ancestorNode Search from this root.
* @param {DOMEventTarget} ancestorNode Search from this root.
* @pararm {string} id ID of the DOM representation of the component.
* @return {?DOMElement} DOM element with the supplied `id`, if one exists.
* @return {DOMEventTarget} DOM node with the supplied `id`.
* @internal
*/
findComponentRoot: function(ancestorNode, id) {
var child = ancestorNode.firstChild;
while (child) {
if (id === child.id) {
return child;
} else if (id.indexOf(child.id) === 0) {
return ReactInstanceHandles.findComponentRoot(child, id);
}
child = child.nextSibling;
}
// Effectively: return null;
},
var firstChildren = [ancestorNode.firstChild];
var childIndex = 0;
/**
* Gets the nearest common ancestor ID of two IDs.
*
* Using this ID scheme, the nearest common ancestor ID is the longest common
* prefix of the two IDs that immediately preceded a "marker" in both strings.
*
* @param {string} oneID
* @param {string} twoID
* @return {string} Nearest common ancestor ID, or the empty string if none.
* @internal
*/
getFirstCommonAncestorID: function(oneID, twoID) {
var minLength = Math.min(oneID.length, twoID.length);
if (minLength === 0) {
return '';
}
var lastCommonMarkerIndex = 0;
// Use `<=` to traverse until the "EOL" of the shorter string.
for (var i = 0; i <= minLength; i++) {
if (isMarker(oneID, i) && isMarker(twoID, i)) {
lastCommonMarkerIndex = i;
} else if (oneID.charAt(i) !== twoID.charAt(i)) {
break;
while (childIndex < firstChildren.length) {
var child = firstChildren[childIndex++];
while (child) {
var childID = ReactID.getID(child);
if (childID) {
if (id === childID) {
return child;
} else if (isAncestorIDOf(childID, id)) {
// If we find a child whose ID is an ancestor of the given ID,
// then we can be sure that we only want to search the subtree
// rooted at this child, so we can throw out the rest of the
// search state.
firstChildren.length = childIndex = 0;
firstChildren.push(child.firstChild);
break;
} else {
// TODO This should not be necessary if the ID hierarchy is
// correct, but is occasionally necessary if the DOM has been
// modified in unexpected ways.
firstChildren.push(child.firstChild);
}
} else {
// If this child had no ID, then there's a chance that it was
// injected automatically by the browser, as when a `<table>`
// element sprouts an extra `<tbody>` child as a side effect of
// `.innerHTML` parsing. Optimistically continue down this
// branch, but not before examining the other siblings.
firstChildren.push(child.firstChild);
}
child = child.nextSibling;
}
}
var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
if (__DEV__) {
console.error(
'Error while invoking `findComponentRoot` with the following ' +
'ancestor node:',
ancestorNode
);
}
invariant(
isValidID(longestCommonID),
'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
oneID,
twoID,
longestCommonID
false,
'findComponentRoot(..., %s): Unable to find element. This probably ' +
'means the DOM was unexpectedly mutated (e.g. by the browser).',
id,
ReactID.getID(ancestorNode)
);
return longestCommonID;
},
/**
* Creates a DOM ID to use when mounting React components.
*
* @param {number} mountPointCount The count of React renders so far.
* @return {string} React root ID.
* @internal
*/
getReactRootID: function(mountPointCount) {
return '.reactRoot[' + mountPointCount + ']';
},
/**
@@ -228,11 +341,11 @@ var ReactInstanceHandles = {
* contains the React component with the supplied DOM ID.
*
* @param {string} id DOM ID of a React component.
* @return {string} DOM ID of the React component that is the root.
* @return {?string} DOM ID of the React component that is the root.
* @internal
*/
getReactRootIDFromNodeID: function(id) {
var regexResult = /\.reactRoot\[[^\]]+\]/.exec(id);
var regexResult = /\.r\[[^\]]+\]/.exec(id);
return regexResult && regexResult[0];
},
@@ -251,15 +364,12 @@ var ReactInstanceHandles = {
* @internal
*/
traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID(
leaveID,
enterID
);
if (longestCommonID !== leaveID) {
traverseParentPath(leaveID, longestCommonID, cb, upArg, false, true);
var ancestorID = getFirstCommonAncestorID(leaveID, enterID);
if (ancestorID !== leaveID) {
traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);
}
if (longestCommonID !== enterID) {
traverseParentPath(longestCommonID, enterID, cb, downArg, true, false);
if (ancestorID !== enterID) {
traverseParentPath(ancestorID, enterID, cb, downArg, true, false);
}
},
@@ -281,45 +391,16 @@ var ReactInstanceHandles = {
},
/**
* Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
* supplied `destinationID`. If they are equal, the ID is returned.
*
* @param {string} ancestorID ID of an ancestor node of `destinationID`.
* @param {string} destinationID ID of the destination node.
* @return {string} Next ID on the path from `ancestorID` to `destinationID`.
* @internal
* Exposed for unit testing.
* @private
*/
nextDescendantID: function(ancestorID, destinationID) {
invariant(
isValidID(ancestorID) && isValidID(destinationID),
'nextDescendantID(%s, %s): Received an invalid React DOM ID.',
ancestorID,
destinationID
);
var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID(
ancestorID,
destinationID
);
invariant(
longestCommonID === ancestorID,
'nextDescendantID(...): React has made an invalid assumption about the ' +
'DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
ancestorID,
destinationID
);
if (ancestorID === destinationID) {
return ancestorID;
}
// Skip over the ancestor and the immediate separator. Traverse until we hit
// another separator or we reach the end of `destinationID`.
var start = ancestorID.length + SEPARATOR_LENGTH;
for (var i = start; i < destinationID.length; i++) {
if (isMarker(destinationID, i)) {
break;
}
}
return destinationID.substr(0, i);
}
_getFirstCommonAncestorID: getFirstCommonAncestorID,
/**
* Exposed for unit testing.
* @private
*/
_getNextDescendantID: getNextDescendantID
};

View File

@@ -18,26 +18,40 @@
"use strict";
var ReactEvent = require('ReactEvent');
var invariant = require('invariant');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
var ReactID = require('ReactID');
var $ = require('$');
var globalMountPointCounter = 0;
/** Mapping from reactRoot DOM ID to React component instance. */
/** Mapping from reactRootID to React component instance. */
var instanceByReactRootID = {};
/** Mapping from reactRoot DOM ID to `container` nodes. */
/** Mapping from reactRootID to `container` nodes. */
var containersByReactRootID = {};
if (__DEV__) {
/** __DEV__-only mapping from reactRootID to root elements. */
var rootElementsByReactRootID = {};
}
/**
* @param {DOMElement} container DOM element that may contain a React component
* @return {?*} DOM element that may have the reactRoot ID, or null.
*/
function getReactRootElementInContainer(container) {
return container && container.firstChild;
}
/**
* @param {DOMElement} container DOM element that may contain a React component.
* @return {?string} A "reactRoot" ID, if a React component is rendered.
*/
function getReactRootID(container) {
return container.firstChild && container.firstChild.id;
var rootElement = getReactRootElementInContainer(container);
return rootElement && ReactID.getID(rootElement);
}
/**
@@ -47,9 +61,9 @@ function getReactRootID(container) {
*
* ReactMount.renderComponent(component, $('container'));
*
* <div id="container"> <-- Supplied `container`.
* <div id=".reactRoot[3]"> <-- Rendered reactRoot of React component.
* // ...
* <div id="container"> <-- Supplied `container`.
* <div data-reactid=".r[3]"> <-- Rendered reactRoot of React
* // ... component.
* </div>
* </div>
*
@@ -79,19 +93,86 @@ var ReactMount = {
},
/**
* Ensures tht the top-level event delegation listener is set up. This will be
* invoked some time before the first time any React component is rendered.
* Ensures that the top-level event delegation listener is set up. This will
* be invoked some time before the first time any React component is rendered.
*
* @param {object} TopLevelCallbackCreator
* @private
*/
prepareTopLevelEvents: function(TopLevelCallbackCreator) {
ReactEvent.ensureListening(
ReactEventEmitter.ensureListening(
ReactMount.useTouchEvents,
TopLevelCallbackCreator
);
},
/**
* Take a component that's already mounted into the DOM and replace its props
* @param {ReactComponent} prevComponent component instance already in the DOM
* @param {ReactComponent} nextComponent component instance to render
* @param {DOMElement} container container to render into
* @param {?function} callback function triggered on completion
*/
_updateRootComponent: function(
prevComponent,
nextComponent,
container,
callback) {
var nextProps = nextComponent.props;
ReactMount.scrollMonitor(container, function() {
prevComponent.replaceProps(nextProps, callback);
});
if (__DEV__) {
// Record the root element in case it later gets transplanted.
rootElementsByReactRootID[getReactRootID(container)] =
getReactRootElementInContainer(container);
}
return prevComponent;
},
/**
* Register a component into the instance map and start the events system.
* @param {ReactComponent} nextComponent component instance to render
* @param {DOMElement} container container to render into
* @return {string} reactRoot ID prefix
*/
_registerComponent: function(nextComponent, container) {
ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
var reactRootID = ReactMount.registerContainer(container);
instanceByReactRootID[reactRootID] = nextComponent;
return reactRootID;
},
/**
* Render a new component into the DOM.
* @param {ReactComponent} nextComponent component instance to render
* @param {DOMElement} container container to render into
* @param {boolean} shouldReuseMarkup if we should skip the markup insertion
* @return {ReactComponent} nextComponent
*/
_renderNewRootComponent: function(
nextComponent,
container,
shouldReuseMarkup) {
var reactRootID = ReactMount._registerComponent(nextComponent, container);
nextComponent.mountComponentIntoNode(
reactRootID,
container,
shouldReuseMarkup
);
if (__DEV__) {
// Record the root element in case it later gets transplanted.
rootElementsByReactRootID[reactRootID] =
getReactRootElementInContainer(container);
}
return nextComponent;
},
/**
* Renders a React component into the DOM in the supplied `container`.
*
@@ -101,45 +182,39 @@ var ReactMount = {
*
* @param {ReactComponent} nextComponent Component instance to render.
* @param {DOMElement} container DOM element to render into.
* @param {?function} callback function triggered on completion
* @return {ReactComponent} Component instance rendered in `container`.
*/
renderComponent: function(nextComponent, container) {
var prevComponent = instanceByReactRootID[getReactRootID(container)];
if (prevComponent) {
if (prevComponent.constructor === nextComponent.constructor) {
var nextProps = nextComponent.props;
ReactMount.scrollMonitor(container, function() {
prevComponent.replaceProps(nextProps);
});
return prevComponent;
renderComponent: function(nextComponent, container, callback) {
var registeredComponent = instanceByReactRootID[getReactRootID(container)];
if (registeredComponent) {
if (registeredComponent.constructor === nextComponent.constructor) {
return ReactMount._updateRootComponent(
registeredComponent,
nextComponent,
container,
callback
);
} else {
ReactMount.unmountAndReleaseReactRootNode(container);
}
}
ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
var reactRootElement = getReactRootElementInContainer(container);
var containerHasReactMarkup =
reactRootElement &&
ReactInstanceHandles.isRenderedByReact(reactRootElement);
var reactRootID = ReactMount.registerContainer(container);
instanceByReactRootID[reactRootID] = nextComponent;
nextComponent.mountComponentIntoNode(reactRootID, container);
return nextComponent;
},
var shouldReuseMarkup = containerHasReactMarkup && !registeredComponent;
/**
* Creates a function that accepts a `container` and renders the supplied
* React component instance into it.
*
* var renderInto = ReactMount.createComponentRenderer(component);
* // ...
* var component = renderInto($('container'));
*
* @param {ReactComponent} component Component instance to render.
* @return {function(DOMElement): ReactComponent}
*/
createComponentRenderer: function(component) {
return function(container) {
return ReactMount.renderComponent(component, container);
};
var component = ReactMount._renderNewRootComponent(
nextComponent,
container,
shouldReuseMarkup
);
callback && callback();
return component;
},
/**
@@ -184,9 +259,7 @@ var ReactMount = {
}
if (!reactRootID) {
// No valid "reactRoot" ID found, create one.
reactRootID = ReactInstanceHandles.getReactRootID(
globalMountPointCounter++
);
reactRootID = ReactInstanceHandles.createReactRootID();
}
containersByReactRootID[reactRootID] = container;
return reactRootID;
@@ -196,14 +269,22 @@ var ReactMount = {
* Unmounts and destroys the React component rendered in the `container`.
*
* @param {DOMElement} container DOM element containing a React component.
* @return {boolean} True if a component was found in and unmounted from
* `container`
*/
unmountAndReleaseReactRootNode: function(container) {
var reactRootID = getReactRootID(container);
var component = instanceByReactRootID[reactRootID];
// TODO: Consider throwing if no `component` was found.
if (!component) {
return false;
}
component.unmountComponentFromNode(container);
delete instanceByReactRootID[reactRootID];
delete containersByReactRootID[reactRootID];
if (__DEV__) {
delete rootElementsByReactRootID[reactRootID];
}
return true;
},
/**
@@ -214,19 +295,46 @@ var ReactMount = {
* @return {?DOMElement} DOM element that contains the `id`.
*/
findReactContainerForID: function(id) {
var reatRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);
// TODO: Consider throwing if `id` is not a valid React element ID.
return containersByReactRootID[reatRootID];
var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);
var container = containersByReactRootID[reactRootID];
if (__DEV__) {
var rootElement = rootElementsByReactRootID[reactRootID];
if (rootElement && rootElement.parentNode !== container) {
invariant(
// Call rawGetID here because getID calls isValid which calls
// findReactContainerForID (this function).
ReactID.rawGetID(rootElement) === reactRootID,
'ReactMount: Root element ID differed from reactRootID.'
);
var containerChild = container.firstChild;
if (containerChild &&
reactRootID === ReactID.rawGetID(containerChild)) {
// If the container has a new child with the same ID as the old
// root element, then rootElementsByReactRootID[reactRootID] is
// just stale and needs to be updated. The case that deserves a
// warning is when the container is empty.
rootElementsByReactRootID[reactRootID] = containerChild;
} else {
console.warn(
'ReactMount: Root element has been removed from its original ' +
'container. New container:', rootElement.parentNode
);
}
}
}
return container;
},
/**
* Given the ID of a DOM node rendered by a React component, finds the root
* DOM node of the React component.
* Finds an element rendered by React with the supplied ID.
*
* @param {string} id ID of a DOM node in the React component.
* @return {?DOMElement} Root DOM node of the React component.
* @return {DOMElement} Root DOM node of the React component.
*/
findReactRenderedDOMNodeSlow: function(id) {
findReactNodeByID: function(id) {
var reactRoot = ReactMount.findReactContainerForID(id);
return ReactInstanceHandles.findComponentRoot(reactRoot, id);
}

View File

@@ -167,8 +167,7 @@ var ReactMultiChildMixin = {
this.enqueueMove(curChild._domIndex, loopDomIndex);
}
curChildrenDOMIndex = Math.max(curChild._domIndex, curChildrenDOMIndex);
!nextChild.props.isStatic &&
curChild.receiveProps(nextChild.props, transaction);
curChild.receiveProps(nextChild.props, transaction);
curChild._domIndex = loopDomIndex;
} else {
if (curChild) { // !shouldUpdate && curChild => delete

View File

@@ -14,16 +14,18 @@
* limitations under the License.
*
* @providesModule ReactNativeComponent
* @typechecks
* @typechecks static-only
*/
"use strict";
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactComponent = require('ReactComponent');
var ReactEvent = require('ReactEvent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactMultiChild = require('ReactMultiChild');
var ReactID = require('ReactID');
var escapeTextForBrowser = require('escapeTextForBrowser');
var flattenChildren = require('flattenChildren');
@@ -32,13 +34,13 @@ var keyOf = require('keyOf');
var merge = require('merge');
var mixInto = require('mixInto');
var putListener = ReactEvent.putListener;
var registrationNames = ReactEvent.registrationNames;
var putListener = ReactEventEmitter.putListener;
var deleteListener = ReactEventEmitter.deleteListener;
var registrationNames = ReactEventEmitter.registrationNames;
// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};
var CONTENT = keyOf({content: null});
var DANGEROUSLY_SET_INNER_HTML = keyOf({dangerouslySetInnerHTML: null});
var STYLE = keyOf({style: null});
@@ -49,14 +51,10 @@ function assertValidProps(props) {
if (!props) {
return;
}
// Note the use of `!=` which checks for null or undefined.
var hasChildren = props.children != null ? 1 : 0;
var hasContent = props.content != null ? 1 : 0;
var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
// Note the use of `==` which checks for null or undefined.
invariant(
hasChildren + hasContent + hasInnerHTML <= 1,
'Can only set one of `children`, `props.content`, or ' +
'`props.dangerouslySetInnerHTML`.'
props.children == null || props.dangerouslySetInnerHTML == null,
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
);
invariant(
props.style == null || typeof props.style === 'object',
@@ -71,7 +69,7 @@ function assertValidProps(props) {
* @extends ReactMultiChild
*/
function ReactNativeComponent(tag, omitClose) {
this._tagOpen = '<' + tag + ' ';
this._tagOpen = '<' + tag;
this._tagClose = omitClose ? '' : '</' + tag + '>';
this.tagName = tag.toUpperCase();
}
@@ -137,7 +135,8 @@ ReactNativeComponent.Mixin = {
}
}
return ret + ' id="' + this._rootNodeID + '">';
var escapedID = escapeTextForBrowser(this._rootNodeID);
return ret + ' ' + ReactID.ATTR_NAME + '="' + escapedID + '">';
},
/**
@@ -155,7 +154,7 @@ ReactNativeComponent.Mixin = {
return innerHTML.__html;
}
} else {
var contentToUse = this.props.content != null ? this.props.content :
var contentToUse =
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var childrenToUse = contentToUse != null ? null : this.props.children;
if (contentToUse != null) {
@@ -170,24 +169,24 @@ ReactNativeComponent.Mixin = {
return '';
},
/**
* Controls a native DOM component after it has already been allocated and
* attached to the DOM. Reconciles the root DOM node, then recurses.
*
* @internal
* @param {object} nextProps
* @param {ReactReconcileTransaction} transaction
*/
receiveProps: function(nextProps, transaction) {
invariant(
this._rootNodeID,
'Trying to control a native dom element without a backing id'
);
assertValidProps(nextProps);
ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
this._updateDOMProperties(nextProps);
this._updateDOMChildren(nextProps, transaction);
this.props = nextProps;
},
/**
* Updates a native DOM component after it has already been allocated and
* attached to the DOM. Reconciles the root DOM node, then recurses.
*
* @param {ReactReconcileTransaction} transaction
* @param {object} prevProps
* @internal
* @overridable
*/
updateComponent: function(transaction, prevProps) {
ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
this._updateDOMProperties(prevProps);
this._updateDOMChildren(prevProps, transaction);
},
/**
@@ -202,11 +201,42 @@ ReactNativeComponent.Mixin = {
* TODO: Benchmark areas that can be improved with caching.
*
* @private
* @param {object} nextProps
* @param {object} lastProps
*/
_updateDOMProperties: function(nextProps) {
var lastProps = this.props;
for (var propKey in nextProps) {
_updateDOMProperties: function(lastProps) {
var nextProps = this.props;
var propKey;
var styleName;
var styleUpdates;
for (propKey in lastProps) {
if (nextProps.hasOwnProperty(propKey) ||
!lastProps.hasOwnProperty(propKey)) {
continue;
}
if (propKey === STYLE) {
var lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// http://jsperf.com/emptying-speed
ReactComponent.DOMIDOperations.updateTextContentByID(
this._rootNodeID,
''
);
} else if (registrationNames[propKey]) {
deleteListener(this._rootNodeID, propKey);
} else {
ReactComponent.DOMIDOperations.deletePropertyByID(
this._rootNodeID,
propKey
);
}
}
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = lastProps[propKey];
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
@@ -216,23 +246,26 @@ ReactNativeComponent.Mixin = {
if (nextProp) {
nextProp = nextProps.style = merge(nextProp);
}
var styleUpdates;
for (var styleName in nextProp) {
if (!nextProp.hasOwnProperty(styleName)) {
continue;
}
if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
if (!styleUpdates) {
styleUpdates = {};
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
for (styleName in lastProp) {
if (lastProp.hasOwnProperty(styleName) &&
!nextProp.hasOwnProperty(styleName)) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
styleUpdates[styleName] = nextProp[styleName];
}
}
if (styleUpdates) {
ReactComponent.DOMIDOperations.updateStylesByID(
this._rootNodeID,
styleUpdates
);
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
if (nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
// Relies on `updateStylesByID` not mutating `styleUpdates`.
styleUpdates = nextProp;
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
var lastHtml = lastProp && lastProp.__html;
@@ -243,14 +276,11 @@ ReactNativeComponent.Mixin = {
nextProp
);
}
} else if (propKey === CONTENT) {
ReactComponent.DOMIDOperations.updateTextContentByID(
this._rootNodeID,
'' + nextProp
);
} else if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, nextProp);
} else {
} else if (
DOMProperty.isStandardName[propKey] ||
DOMProperty.isCustomAttribute(propKey)) {
ReactComponent.DOMIDOperations.updatePropertyByID(
this._rootNodeID,
propKey,
@@ -258,33 +288,33 @@ ReactNativeComponent.Mixin = {
);
}
}
if (styleUpdates) {
ReactComponent.DOMIDOperations.updateStylesByID(
this._rootNodeID,
styleUpdates
);
}
},
/**
* Reconciles the children with the various properties that affect the
* children content.
*
* @param {object} nextProps
* @param {object} lastProps
* @param {ReactReconcileTransaction} transaction
*/
_updateDOMChildren: function(nextProps, transaction) {
var thisPropsContentType = typeof this.props.content;
var thisPropsContentEmpty =
this.props.content == null || thisPropsContentType === 'boolean';
var nextPropsContentType = typeof nextProps.content;
var nextPropsContentEmpty =
nextProps.content == null || nextPropsContentType === 'boolean';
_updateDOMChildren: function(lastProps, transaction) {
var nextProps = this.props;
var lastUsedContent = !thisPropsContentEmpty ? this.props.content :
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var contentToUse = !nextPropsContentEmpty ? nextProps.content :
var lastUsedContent =
CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
var contentToUse =
CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
// Note the use of `!=` which checks for null or undefined.
var lastUsedChildren =
lastUsedContent != null ? null : this.props.children;
lastUsedContent != null ? null : lastProps.children;
var childrenToUse = contentToUse != null ? null : nextProps.children;
if (contentToUse != null) {
@@ -317,9 +347,9 @@ ReactNativeComponent.Mixin = {
* @internal
*/
unmountComponent: function() {
ReactEventEmitter.deleteAllListeners(this._rootNodeID);
ReactComponent.Mixin.unmountComponent.call(this);
this.unmountMultiChild();
ReactEvent.deleteAllListeners(this._rootNodeID);
}
};

View File

@@ -37,9 +37,9 @@ var invariant = require('invariant');
* </div>
* );
* },
* handleClick: React.autoBind(function() {
* handleClick: function() {
* this.refs.custom.handleClick();
* }),
* },
* componentDidMount: function() {
* this.refs.custom.initialize();
* }

View File

@@ -44,13 +44,17 @@ function createTransferStrategy(mergeStrategy) {
*/
var TransferStrategies = {
/**
* Never transfer the `ref` prop.
* Never transfer `children`.
*/
ref: emptyFunction,
children: emptyFunction,
/**
* Transfer the `className` prop by merging them.
*/
className: createTransferStrategy(joinClasses),
/**
* Never transfer the `ref` prop.
*/
ref: emptyFunction,
/**
* Transfer the `style` prop (which is an object) by merging them.
*/

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactProps
* @providesModule ReactPropTypes
*/
"use strict";
@@ -25,9 +25,9 @@ var invariant = require('invariant');
* Collection of methods that allow declaration and validation of props that are
* supplied to React components. Example usage:
*
* var Props = require('ReactProps');
* var Props = require('ReactPropTypes');
* var MyArticle = React.createClass({
* props: {
* propTypes: {
* // An optional string prop named "description".
* description: Props.string,
*
@@ -43,14 +43,14 @@ var invariant = require('invariant');
* A more formal specification of how these methods are used:
*
* type := array|bool|object|number|string|oneOf([...])|instanceOf(...)
* decl := ReactProps.{type}(.isRequired)?
* decl := ReactPropTypes.{type}(.isRequired)?
*
* Each and every declaration produces a function with the same signature. This
* allows the creation of custom validation functions. For example:
*
* var Props = require('ReactProps');
* var Props = require('ReactPropTypes');
* var MyLink = React.createClass({
* props: {
* propTypes: {
* // An optional string or URI prop named "href".
* href: function(props, propName, componentName) {
* var propValue = props[propName];
@@ -73,6 +73,7 @@ var Props = {
array: createPrimitiveTypeChecker('array'),
bool: createPrimitiveTypeChecker('boolean'),
func: createPrimitiveTypeChecker('function'),
number: createPrimitiveTypeChecker('number'),
object: createPrimitiveTypeChecker('object'),
string: createPrimitiveTypeChecker('string'),

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*
* @providesModule ReactReconcileTransaction
* @typechecks
* @typechecks static-only
*/
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var PooledClass = require('PooledClass');
var ReactEvent = require('ReactEvent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactInputSelection = require('ReactInputSelection');
var ReactOnDOMReady = require('ReactOnDOMReady');
var Transaction = require('Transaction');
@@ -50,21 +50,21 @@ var SELECTION_RESTORATION = {
*/
var EVENT_SUPPRESSION = {
/**
* @return {boolean} The enabled status of `ReactEvent` before the
* @return {boolean} The enabled status of `ReactEventEmitter` before the
* reconciliation.
*/
initialize: function() {
var currentlyEnabled = ReactEvent.isEnabled();
ReactEvent.setEnabled(false);
var currentlyEnabled = ReactEventEmitter.isEnabled();
ReactEventEmitter.setEnabled(false);
return currentlyEnabled;
},
/**
* @param {boolean} previouslyEnabled The enabled status of `ReactEvent`
* @param {boolean} previouslyEnabled Enabled status of `ReactEventEmitter`
* before the reconciliation occured. `close` restores the previous value.
*/
close: function(previouslyEnabled) {
ReactEvent.setEnabled(previouslyEnabled);
ReactEventEmitter.setEnabled(previouslyEnabled);
}
};

View File

@@ -14,12 +14,13 @@
* limitations under the License.
*
* @providesModule ReactTextComponent
* @typechecks
* @typechecks static-only
*/
"use strict";
var ReactComponent = require('ReactComponent');
var ReactID = require('ReactID');
var escapeTextForBrowser = require('escapeTextForBrowser');
var mixInto = require('mixInto');
@@ -57,7 +58,7 @@ mixInto(ReactTextComponent, {
mountComponent: function(rootID) {
ReactComponent.Mixin.mountComponent.call(this, rootID);
return (
'<span id="' + rootID + '">' +
'<span ' + ReactID.ATTR_NAME + '="' + rootID + '">' +
escapeTextForBrowser(this.props.text) +
'</span>'
);

105
src/core/ReactUpdates.js Normal file
View File

@@ -0,0 +1,105 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactUpdates
*/
"use strict";
var invariant = require('invariant');
var isBatchingUpdates = false;
var dirtyComponents = [];
/**
* Call the provided function in a context within which calls to `setState` and
* friends are batched such that components aren't updated unnecessarily.
*/
function batchedUpdates(callback) {
if (isBatchingUpdates) {
// We're already executing in an environment where updates will be batched,
// so this is a no-op.
callback();
return;
}
isBatchingUpdates = true;
try {
callback();
// TODO: Sort components by depth such that parent components update first
for (var i = 0; i < dirtyComponents.length; i++) {
// If a component is unmounted before pending changes apply, ignore them
// TODO: Queue unmounts in the same list to avoid this happening at all
var component = dirtyComponents[i];
if (component.isMounted()) {
// If performUpdateIfNecessary happens to enqueue any new updates, we
// shouldn't execute the callbacks until the next render happens, so
// stash the callbacks first
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
component.performUpdateIfNecessary();
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
callbacks[j]();
}
}
}
}
} catch (error) {
// IE8 requires `catch` in order to use `finally`.
throw error;
} finally {
dirtyComponents.length = 0;
isBatchingUpdates = false;
}
}
/**
* Mark a component as needing a rerender, adding an optional callback to a
* list of functions which will be executed once the rerender occurs.
*/
function enqueueUpdate(component, callback) {
invariant(
!callback || typeof callback === "function",
'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' +
'`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
'isn\'t callable.'
);
if (!isBatchingUpdates) {
component.performUpdateIfNecessary();
callback && callback();
return;
}
dirtyComponents.push(component);
if (callback) {
if (component._pendingCallbacks) {
component._pendingCallbacks.push(callback);
} else {
component._pendingCallbacks = [callback];
}
}
}
var ReactUpdates = {
batchedUpdates: batchedUpdates,
enqueueUpdate: enqueueUpdate
};
module.exports = ReactUpdates;

View File

@@ -21,26 +21,32 @@
var mocks = require('mocks');
var React = require('React');
var ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
var ReactTestUtils = require('ReactTestUtils');
var reactComponentExpect = require('reactComponentExpect');
describe('React.autoBind', function() {
// TODO: Test render and all stock methods.
describe('autobinding', function() {
it('Holds reference to instance', function() {
var mouseDidEnter = mocks.getMockFunction();
var mouseDidLeave = mocks.getMockFunction();
var mouseDidClick = mocks.getMockFunction();
var didBadIdea = mocks.getMockFunction();
var TestBindComponent = React.createClass({
onMouseEnter: mouseDidEnter,
onMouseLeave: mouseDidLeave,
onClick: React.autoBind(mouseDidClick),
getInitialState: function() {
return {something: 'hi'};
},
onMouseEnter: ReactDoNotBindDeprecated.doNotBind(mouseDidEnter),
onMouseLeave: ReactDoNotBindDeprecated.doNotBind(mouseDidLeave),
onClick: mouseDidClick,
// autoBind needs to be on the top-level spec.
// auto binding only occurs on top level functions in class defs.
badIdeas: {
badBind: React.autoBind(didBadIdea)
badBind: function() {
this.state.something;
}
},
render: function() {
@@ -48,7 +54,8 @@ describe('React.autoBind', function() {
<div
onMouseEnter={this.onMouseEnter.bind(this)}
onMouseLeave={this.onMouseLeave}
onClick={this.onClick} />
onClick={this.onClick}
/>
);
}
});
@@ -103,7 +110,7 @@ describe('React.autoBind', function() {
var mouseDidClick = mocks.getMockFunction();
var TestMixin = {
onClick: React.autoBind(mouseDidClick)
onClick: mouseDidClick
};
var TestBindComponent = React.createClass({

View File

@@ -31,6 +31,24 @@ describe('ReactComponent', function() {
reactComponentExpect = require('reactComponentExpect');
});
it('should throw on invalid render targets', function() {
var container = document.createElement('div');
// jQuery objects are basically arrays; people often pass them in by mistake
expect(function() {
React.renderComponent(<div></div>, [container]);
}).toThrow(
'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
'not a DOM element.'
);
expect(function() {
React.renderComponent(<div></div>, null);
}).toThrow(
'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
'not a DOM element.'
);
});
it('should throw when supplying a ref outside of render method', function() {
var instance = <div ref="badDiv" />;
expect(function() {
@@ -95,4 +113,23 @@ describe('ReactComponent', function() {
ReactTestUtils.renderIntoDocument(instance);
});
it('should correctly determine if a component is mounted', function() {
var Component = React.createClass({
componentWillMount: function() {
expect(this.isMounted()).toBeFalsy();
},
componentDidMount: function() {
expect(this.isMounted()).toBeTruthy();
},
render: function() {
return <div/>;
}
});
var instance = <Component />;
expect(instance.isMounted()).toBeFalsy();
ReactTestUtils.renderIntoDocument(instance);
expect(instance.isMounted()).toBeTruthy();
});
});

View File

@@ -286,6 +286,8 @@ describe('ReactComponentLifeCycle', function() {
if (isInitialRender) {
this._testJournal.stateInInitialRender = clone(this.state);
this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;
this._testJournal.compositeLifeCycleInInitialRender =
this._compositeLifeCycleState;
} else {
this._testJournal.stateInLaterRender = clone(this.state);
this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;
@@ -319,7 +321,7 @@ describe('ReactComponentLifeCycle', function() {
GET_INIT_STATE_RETURN_VAL
);
expect(instance._testJournal.lifeCycleAtStartOfGetInitialState)
.toBe(ComponentLifeCycle.UNMOUNTED);
.toBe(ComponentLifeCycle.MOUNTED);
expect(instance._testJournal.compositeLifeCycleAtStartOfGetInitialState)
.toBe(CompositeComponentLifeCycle.MOUNTING);
@@ -328,7 +330,7 @@ describe('ReactComponentLifeCycle', function() {
instance._testJournal.returnedFromGetInitialState
);
expect(instance._testJournal.lifeCycleAtStartOfWillMount)
.toBe(ComponentLifeCycle.UNMOUNTED);
.toBe(ComponentLifeCycle.MOUNTED);
expect(instance._testJournal.compositeLifeCycleAtStartOfWillMount)
.toBe(CompositeComponentLifeCycle.MOUNTING);
@@ -343,7 +345,10 @@ describe('ReactComponentLifeCycle', function() {
expect(instance._testJournal.stateInInitialRender)
.toEqual(INIT_RENDER_STATE);
expect(instance._testJournal.lifeCycleInInitialRender).toBe(
ComponentLifeCycle.UNMOUNTED
ComponentLifeCycle.MOUNTED
);
expect(instance._testJournal.compositeLifeCycleInInitialRender).toBe(
CompositeComponentLifeCycle.MOUNTING
);
expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
@@ -429,5 +434,69 @@ describe('ReactComponentLifeCycle', function() {
expect(instance.state.stateField).toBe('goodbye');
});
it('should call nested lifecycle methods in the right order', function() {
var log;
var logger = function(msg) {
return function() {
// return true for shouldComponentUpdate
log.push(msg);
return true;
};
};
var Outer = React.createClass({
render: function() {
return <div><Inner x={this.props.x} /></div>;
},
componentWillMount: logger('outer componentWillMount'),
componentDidMount: logger('outer componentDidMount'),
componentWillReceiveProps: logger('outer componentWillReceiveProps'),
shouldComponentUpdate: logger('outer shouldComponentUpdate'),
componentWillUpdate: logger('outer componentWillUpdate'),
componentDidUpdate: logger('outer componentDidUpdate'),
componentWillUnmount: logger('outer componentWillUnmount')
});
var Inner = React.createClass({
render: function() {
return <span>{this.props.x}</span>;
},
componentWillMount: logger('inner componentWillMount'),
componentDidMount: logger('inner componentDidMount'),
componentWillReceiveProps: logger('inner componentWillReceiveProps'),
shouldComponentUpdate: logger('inner shouldComponentUpdate'),
componentWillUpdate: logger('inner componentWillUpdate'),
componentDidUpdate: logger('inner componentDidUpdate'),
componentWillUnmount: logger('inner componentWillUnmount')
});
var instance;
log = [];
instance = ReactTestUtils.renderIntoDocument(<Outer x={17} />);
expect(log).toEqual([
'outer componentWillMount',
'inner componentWillMount',
'inner componentDidMount',
'outer componentDidMount'
]);
log = [];
instance.setProps({x: 42});
expect(log).toEqual([
'outer componentWillReceiveProps',
'outer shouldComponentUpdate',
'outer componentWillUpdate',
'inner componentWillReceiveProps',
'inner shouldComponentUpdate',
'inner componentWillUpdate',
'inner componentDidUpdate',
'outer componentDidUpdate'
]);
log = [];
instance.unmountComponent();
expect(log).toEqual([
'outer componentWillUnmount',
'inner componentWillUnmount'
]);
});
});

View File

@@ -20,11 +20,13 @@
"use strict";
var MorphingComponent;
var MorphingAutoBindComponent;
var ChildUpdates;
var React;
var ReactProps;
var ReactCurrentOwner;
var ReactPropTypes;
var ReactTestUtils;
var ReactID;
var ReactDoNotBindDeprecated;
var cx;
var reactComponentExpect;
@@ -35,8 +37,11 @@ describe('ReactCompositeComponent', function() {
cx = require('cx');
reactComponentExpect = require('reactComponentExpect');
React = require('React');
ReactProps = require('ReactProps');
ReactCurrentOwner = require('ReactCurrentOwner');
ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
ReactPropTypes = require('ReactPropTypes');
ReactTestUtils = require('ReactTestUtils');
ReactID = require('ReactID');
MorphingComponent = React.createClass({
getInitialState: function() {
@@ -48,29 +53,13 @@ describe('ReactCompositeComponent', function() {
},
render: function() {
var toggleActivatedState = this._toggleActivatedState.bind(this);
var toggleActivatedState = this._toggleActivatedState;
return !this.state.activated ?
<a ref="x" onClick={toggleActivatedState} /> :
<b ref="x" onClick={toggleActivatedState} />;
}
});
MorphingAutoBindComponent = React.createClass({
getInitialState: function() {
return {activated: false};
},
_toggleActivatedState: React.autoBind(function() {
this.setState({activated: !this.state.activated});
}),
render: function() {
return !this.state.activated ?
<a ref="x" onClick={this._toggleActivatedState} /> :
<b ref="x" onClick={this._toggleActivatedState} />;
}
});
/**
* We'll use this to ensure that an old version is not cached when it is
* reallocated again.
@@ -132,24 +121,6 @@ describe('ReactCompositeComponent', function() {
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
});
it('should behave the same with React.autoBind', function() {
var instance = <MorphingAutoBindComponent />;
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild()
.toBeDOMComponentWithTag('a');
var renderedChild = reactComponentExpect(instance)
.expectRenderedChild()
.instance();
ReactTestUtils.Simulate.click(renderedChild);
reactComponentExpect(instance)
.expectRenderedChild()
.toBeDOMComponentWithTag('b');
});
it('should not cache old DOM nodes when switching constructors', function() {
var instance = <ChildUpdates renderAnchor={true} anchorClassOn={false}/>;
ReactTestUtils.renderIntoDocument(instance);
@@ -158,20 +129,23 @@ describe('ReactCompositeComponent', function() {
// rerender
instance.setProps({renderAnchor: true, anchorClassOn: false});
var anchorID = instance.getAnchorID();
var actualDOMAnchorNode = document.getElementById(anchorID);
var actualDOMAnchorNode = ReactID.getNode(anchorID);
expect(actualDOMAnchorNode.className).toBe('');
});
it('should auto bind methods and values correctly', function() {
var RETURN_VALUE_AFTER_MOUNT = 'returnValue';
var ComponentClass = React.createClass({
getInitialState: function() {
return {
valueToReturn: RETURN_VALUE_AFTER_MOUNT
};
return {valueToReturn: 'hi'};
},
methodBoundOnMount: React.autoBind(function() {
return this.state.valueToReturn;
methodToBeExplicitlyBound: function() {
return this;
},
methodAutoBound: function() {
return this;
},
methodExplicitlyNotBound: ReactDoNotBindDeprecated.doNotBind(function() {
return this;
}),
render: function() {
return <div> </div>;
@@ -179,25 +153,87 @@ describe('ReactCompositeComponent', function() {
});
var instance = <ComponentClass />;
// Autobound methods will throw before mounting.
// These are controversial assertions for now, they just exist
// because existing code depends on these assumptions.
expect(function() {
instance.methodBoundOnMount();
}).toThrow();
instance.methodToBeExplicitlyBound.bind(instance)();
}).not.toThrow();
expect(function() {
instance.methodAutoBound();
}).not.toThrow();
expect(function() {
instance.methodExplicitlyNotBound();
}).not.toThrow();
// Next, prove that once mounted, the scope is bound correctly to the actual
// component.
ReactTestUtils.renderIntoDocument(instance);
var retValAfterMount = instance.methodBoundOnMount();
expect(retValAfterMount).toBe(RETURN_VALUE_AFTER_MOUNT);
var retValAfterMountWithCrazyScope =
instance.methodBoundOnMount.call({thisIsACrazyScope:null});
expect(retValAfterMountWithCrazyScope).toBe(RETURN_VALUE_AFTER_MOUNT);
var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance);
var autoBound = instance.methodAutoBound;
var explicitlyNotBound = instance.methodExplicitlyNotBound;
var context = {};
expect(explicitlyBound.call(context)).toBe(instance);
expect(autoBound.call(context)).toBe(instance);
expect(explicitlyNotBound.call(context)).toBe(context);
expect(explicitlyBound.call(instance)).toBe(instance);
expect(autoBound.call(instance)).toBe(instance);
expect(explicitlyNotBound.call(instance)).toBe(instance);
});
it('should normalize props with default values', function() {
var Component = React.createClass({
propTypes: {key: ReactPropTypes.string.isRequired},
getDefaultProps: function() {
return {key: 'testKey'};
},
getInitialState: function() {
return {key: this.props.key + 'State'};
},
render: function() {
return <span>{this.props.key}</span>;
}
});
var instance = <Component />;
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance).scalarPropsEqual({key: 'testKey'});
reactComponentExpect(instance).scalarStateEqual({key: 'testKeyState'});
expect(function() {
ReactTestUtils.renderIntoDocument(<Component key={null} />);
}).toThrow(
'Invariant Violation: Required prop `key` was not specified in ' +
'`Component`.'
);
});
it('should check default prop values', function() {
var Component = React.createClass({
propTypes: {key: ReactPropTypes.string.isRequired},
getDefaultProps: function() {
return {key: null};
},
render: function() {
return <span>{this.props.key}</span>;
}
});
var instance = <Component />;
expect(function() {
ReactTestUtils.renderIntoDocument(instance);
}).toThrow(
'Invariant Violation: Required prop `key` was not specified in ' +
'`Component`.'
);
});
it('should check declared prop types', function() {
var Component = React.createClass({
props: {
key: ReactProps.string.isRequired
propTypes: {
key: ReactPropTypes.string.isRequired
},
render: function() {
return <span>{this.props.key}</span>;
@@ -223,4 +259,53 @@ describe('ReactCompositeComponent', function() {
}).not.toThrow();
});
it('should not allow `forceUpdate` on unmounted components', function() {
var container = document.createElement('div');
document.documentElement.appendChild(container);
var Component = React.createClass({
render: function() {
return <div />;
}
});
var instance = <Component />;
expect(function() {
instance.forceUpdate();
}).toThrow(
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
'mounted or mounting components.'
);
React.renderComponent(instance, container);
expect(function() {
instance.forceUpdate();
}).not.toThrow();
React.unmountAndReleaseReactRootNode(container);
expect(function() {
instance.forceUpdate();
}).toThrow(
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
'mounted or mounting components.'
);
});
it('should cleanup even if render() fatals', function() {
var BadComponent = React.createClass({
render: function() {
throw new Error();
}
});
var instance = <BadComponent />;
expect(ReactCurrentOwner.current).toBe(null);
expect(function() {
ReactTestUtils.renderIntoDocument(instance);
}).toThrow();
expect(ReactCurrentOwner.current).toBe(null);
});
});

View File

@@ -26,6 +26,9 @@ var ReactTestUtils;
var reactComponentExpect;
var TestComponent;
var TestComponentWithPropTypes;
var mixinPropValidator;
var componentPropValidator;
describe('ReactCompositeComponent-mixin', function() {
@@ -33,6 +36,8 @@ describe('ReactCompositeComponent-mixin', function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
reactComponentExpect = require('reactComponentExpect');
mixinPropValidator = mocks.getMockFunction();
componentPropValidator = mocks.getMockFunction();
var MixinA = {
componentDidMount: function() {
@@ -53,8 +58,14 @@ describe('ReactCompositeComponent-mixin', function() {
}
};
var MixinD = {
propTypes: {
value: mixinPropValidator
}
};
TestComponent = React.createClass({
mixins: [MixinB, MixinC],
mixins: [MixinB, MixinC, MixinD],
componentDidMount: function() {
this.props.listener('Component didMount');
@@ -65,6 +76,15 @@ describe('ReactCompositeComponent-mixin', function() {
}
});
TestComponentWithPropTypes = React.createClass({
mixins: [MixinD],
propTypes: {
value: componentPropValidator
},
render: function() {
return <div />;
}
});
});
it('should support chaining delegate functions', function() {
@@ -79,4 +99,22 @@ describe('ReactCompositeComponent-mixin', function() {
['Component didMount']
]);
});
it('should validate prop types via mixins', function() {
expect(TestComponent.componentConstructor.propTypes).toBeDefined();
expect(TestComponent.componentConstructor.propTypes.value)
.toBe(mixinPropValidator);
});
it('should override mixin prop types with class prop types', function() {
// Sanity check...
expect(componentPropValidator).toNotBe(mixinPropValidator);
// Actually check...
expect(TestComponentWithPropTypes.componentConstructor.propTypes)
.toBeDefined();
expect(TestComponentWithPropTypes.componentConstructor.propTypes.value)
.toNotBe(mixinPropValidator);
expect(TestComponentWithPropTypes.componentConstructor.propTypes.value)
.toBe(componentPropValidator);
});
});

View File

@@ -53,10 +53,10 @@ describe('ReactCompositeComponent-spec', function() {
.toBe('TestComponent');
});
it('should copy prop declarations onto the Constructor', function() {
it('should copy prop types onto the Constructor', function() {
var propValidator = mocks.getMockFunction();
var TestComponent = React.createClass({
props: {
propTypes: {
value: propValidator
},
render: function() {
@@ -64,8 +64,8 @@ describe('ReactCompositeComponent-spec', function() {
}
});
expect(TestComponent.componentConstructor.propDeclarations).toBeDefined();
expect(TestComponent.componentConstructor.propDeclarations.value)
expect(TestComponent.componentConstructor.propTypes).toBeDefined();
expect(TestComponent.componentConstructor.propTypes.value)
.toBe(propValidator);
});
});

View File

@@ -51,8 +51,8 @@ describe('ReactCompositeComponent-transferProps', function() {
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild(instance)
.toBeDOMComponentWithTag('input')
.expectRenderedChild()
.toBeComponentOfType(React.DOM.input)
.scalarPropsEqual({
className: 'textinput',
style: {display: 'block'},
@@ -66,8 +66,8 @@ describe('ReactCompositeComponent-transferProps', function() {
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild(instance)
.toBeDOMComponentWithTag('input')
.expectRenderedChild()
.toBeComponentOfType(React.DOM.input)
.scalarPropsEqual({placeholder: 'Type here...'});
});
@@ -80,8 +80,8 @@ describe('ReactCompositeComponent-transferProps', function() {
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild(instance)
.toBeDOMComponentWithTag('input')
.expectRenderedChild()
.toBeComponentOfType(React.DOM.input)
.scalarPropsEqual({
className: 'textinput hidden_elem',
style: {
@@ -91,6 +91,25 @@ describe('ReactCompositeComponent-transferProps', function() {
});
});
it('should not transfer children', function() {
var ChildrenTestComponent = React.createClass({
render: function() {
return this.transferPropsTo(<div />);
}
});
var instance =
<ChildrenTestComponent>
<span>Hello!</span>
</ChildrenTestComponent>;
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild()
.toBeDOMComponentWithTag('div')
.toBeDOMComponentWithNoChildren();
});
it('should not transfer ref', function() {
var RefTestComponent = React.createClass({
render: function() {

View File

@@ -17,12 +17,15 @@
* @emails react-core
*/
/*jslint evil: true */
"use strict";
var React = require('React');
var ReactDOM = require('ReactDOM');
var ReactTestUtils = require('ReactTestUtils');
var React = require('React');
var ReactID = require('ReactID');
describe('ref swapping', function() {
// TODO: uncomment this test once we can run in phantom, which
@@ -58,7 +61,7 @@ describe('ref swapping', function() {
var argDiv = ReactTestUtils.renderIntoDocument(
ReactDOM.div(null, 'child')
);
var argNode = document.getElementById(argDiv._rootNodeID);
var argNode = ReactID.getNode(argDiv._rootNodeID);
expect(argNode.innerHTML).toBe('child');
});
@@ -66,7 +69,7 @@ describe('ref swapping', function() {
var conflictDiv = ReactTestUtils.renderIntoDocument(
ReactDOM.div({children: 'fakechild'}, 'child')
);
var conflictNode = document.getElementById(conflictDiv._rootNodeID);
var conflictNode = ReactID.getNode(conflictDiv._rootNodeID);
expect(conflictNode.innerHTML).toBe('child');
});
@@ -81,31 +84,34 @@ describe('ref swapping', function() {
theBird: <div class="bird" />
}}</div>
);
// Warm the cache with theDog
myDiv.setProps({
children: {
theDog: <div class="dogbeforedelete" />, // Warm the cache with theDog
theDog: <div class="dogbeforedelete" />,
theBird: <div class="bird" />
}
});
myDiv.setProps({
children: { // This better purge that cache
theBird: <div class="bird" />
}
});
// Now, put the dog back.
// Remove theDog - this should purge the cache
myDiv.setProps({
children: {
theDog: <div class="dog" />, // This is a different node than before
theBird: <div class="bird" />
}
});
// Now, put theDog back. It's now a different DOM node.
myDiv.setProps({
children: { // className changed to bigdog.
theDog: <div class="bigdog" />, // but will it use the proper element
children: {
theDog: <div class="dog" />,
theBird: <div class="bird" />
}
});
var root = document.getElementById(myDiv._rootNodeID);
// Change the className of theDog. It will use the same element
myDiv.setProps({
children: {
theDog: <div class="bigdog" />,
theBird: <div class="bird" />
}
});
var root = ReactID.getNode(myDiv._rootNodeID);
var dog = root.childNodes[0];
expect(dog.className).toBe('bigdog');
});

View File

@@ -23,23 +23,23 @@
describe('ReactDOMIDOperations', function() {
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactDOMNodeCache = require('ReactDOMNodeCache');
var ReactID = require('ReactID');
var keyOf = require('keyOf');
it('should disallow updating special properties', function() {
spyOn(ReactDOMNodeCache, "getCachedNodeByID");
spyOn(ReactID, "getNode");
spyOn(DOMPropertyOperations, "setValueForProperty");
expect(function() {
ReactDOMIDOperations.updatePropertyByID(
'testID',
keyOf({content: null}),
'testContent'
keyOf({dangerouslySetInnerHTML: null}),
{__html: 'testContent'}
);
}).toThrow();
expect(
ReactDOMNodeCache.getCachedNodeByID.argsForCall[0][0]
ReactID.getNode.argsForCall[0][0]
).toBe('testID');
expect(
@@ -49,7 +49,7 @@ describe('ReactDOMIDOperations', function() {
it('should update innerHTML and special-case whitespace', function() {
var stubNode = document.createElement('div');
spyOn(ReactDOMNodeCache, "getCachedNodeByID").andReturn(stubNode);
spyOn(ReactID, "getNode").andReturn(stubNode);
ReactDOMIDOperations.updateInnerHTMLByID(
'testID',
@@ -57,7 +57,7 @@ describe('ReactDOMIDOperations', function() {
);
expect(
ReactDOMNodeCache.getCachedNodeByID.argsForCall[0][0]
ReactID.getNode.argsForCall[0][0]
).toBe('testID');
expect(stubNode.innerHTML).toBe('&nbsp;testContent');

View File

@@ -22,7 +22,8 @@ require('mock-modules')
.dontMock('BrowserScroll')
.dontMock('CallbackRegistry')
.dontMock('EventPluginHub')
.dontMock('ReactEvent')
.dontMock('ReactID')
.dontMock('ReactEventEmitter')
.dontMock('ReactInstanceHandles')
.dontMock('EventPluginHub')
.dontMock('TapEventPlugin')
@@ -34,7 +35,10 @@ var keyOf = require('keyOf');
var mocks = require('mocks');
var EventPluginHub;
var ReactEvent;
var ReactID = require('ReactID');
var getID = ReactID.getID;
var setID = ReactID.setID;
var ReactEventEmitter;
var ReactEventTopLevelCallback;
var ReactTestUtils;
var TapEventPlugin;
@@ -58,39 +62,42 @@ var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null});
/**
* Since `ReactEvent` is fairly well separated from the DOM, we can test almost
* all of `ReactEvent` without ever rendering anything in the DOM. As long as we
* provide IDs that follow `React's` conventional id namespace hierarchy.
* The only reason why we create these DOM nodes, is so that when we feed them
* into `ReactEvent` (through `ReactTestUtils`), the event handlers may receive
* a DOM node to inspect.
* Since `ReactEventEmitter` is fairly well separated from the DOM, we can test
* almost all of `ReactEventEmitter` without ever rendering anything in the DOM.
* As long as we provide IDs that follow `React's` conventional id namespace
* hierarchy. The only reason why we create these DOM nodes, is so that when we
* feed them into `ReactEventEmitter` (through `ReactTestUtils`), the event
* handlers may receive a DOM node to inspect.
*/
var CHILD = document.createElement('div');
var PARENT = document.createElement('div');
var GRANDPARENT = document.createElement('div');
CHILD.id = '.reactRoot.[0].[0].[0]';
PARENT.id = '.reactRoot.[0].[0]';
GRANDPARENT.id = '.reactRoot.[0]';
setID(CHILD, '.reactRoot.[0].[0].[0]');
setID(PARENT, '.reactRoot.[0].[0]');
setID(GRANDPARENT, '.reactRoot.[0]');
function registerSimpleTestHandler() {
ReactEvent.putListener(CHILD.id, ON_CLICK_KEY, LISTENER);
var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toEqual(LISTENER);
return ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
return ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
}
describe('ReactEvent', function() {
describe('ReactEventEmitter', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
EventPluginHub = require('EventPluginHub');
TapEventPlugin = require('TapEventPlugin');
ReactEvent = require('ReactEvent');
ReactID = require('ReactID');
getID = ReactID.getID;
setID = ReactID.setID;
ReactEventEmitter = require('ReactEventEmitter');
ReactTestUtils = require('ReactTestUtils');
ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
idCallOrder = [];
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
ReactEvent.ensureListening(false, ReactEventTopLevelCallback);
ReactEventEmitter.ensureListening(false, ReactEventTopLevelCallback);
EventPluginHub.injection.injectEventPluginsByName({
TapEventPlugin: TapEventPlugin
});
@@ -98,20 +105,20 @@ describe('ReactEvent', function() {
it('should store a listener correctly', function() {
registerSimpleTestHandler();
var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toBe(LISTENER);
});
it('should retrieve a listener correctly', function() {
registerSimpleTestHandler();
var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toEqual(LISTENER);
});
it('should clear all handlers when asked to', function() {
registerSimpleTestHandler();
ReactEvent.deleteAllListeners(CHILD.id);
var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
ReactEventEmitter.deleteAllListeners(getID(CHILD));
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toBe(undefined);
});
@@ -121,101 +128,101 @@ describe('ReactEvent', function() {
expect(LISTENER.mock.calls.length).toBe(1);
});
it('should not invoke handlers when ReactEvent is disabled', function() {
it('should not invoke handlers if ReactEventEmitter is disabled', function() {
registerSimpleTestHandler();
ReactEvent.setEnabled(false);
ReactEventEmitter.setEnabled(false);
ReactTestUtils.Simulate.click(CHILD);
expect(LISTENER.mock.calls.length).toBe(0);
ReactEvent.setEnabled(true);
ReactEventEmitter.setEnabled(true);
ReactTestUtils.Simulate.click(CHILD);
expect(LISTENER.mock.calls.length).toBe(1);
});
it('should bubble simply', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_CLICK_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactEvent.putListener(
PARENT.id,
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEvent.putListener(
GRANDPARENT.id,
ReactEventEmitter.putListener(
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[1]).toBe(PARENT.id);
expect(idCallOrder[2]).toBe(GRANDPARENT.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
});
it('should support stopPropagation()', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_CLICK_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactEvent.putListener(
PARENT.id,
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
recordIDAndStopPropagation.bind(null, PARENT.id)
recordIDAndStopPropagation.bind(null, getID(PARENT))
);
ReactEvent.putListener(
GRANDPARENT.id,
ReactEventEmitter.putListener(
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(2);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[1]).toBe(PARENT.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
});
it('should stop after first dispatch if stopPropagation', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_CLICK_KEY,
recordIDAndStopPropagation.bind(null, CHILD.id)
recordIDAndStopPropagation.bind(null, getID(CHILD))
);
ReactEvent.putListener(
PARENT.id,
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEvent.putListener(
GRANDPARENT.id,
ReactEventEmitter.putListener(
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
it('should stopPropagation if false is returned', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_CLICK_KEY,
recordIDAndReturnFalse.bind(null, CHILD.id)
recordIDAndReturnFalse.bind(null, getID(CHILD))
);
ReactEvent.putListener(
PARENT.id,
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEvent.putListener(
GRANDPARENT.id,
ReactEventEmitter.putListener(
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
/**
@@ -229,30 +236,38 @@ describe('ReactEvent', function() {
it('should invoke handlers that were removed while bubbling', function() {
var handleParentClick = mocks.getMockFunction();
var handleChildClick = function(abstractEvent) {
ReactEvent.deleteAllListeners(PARENT.id);
var handleChildClick = function(event) {
ReactEventEmitter.deleteAllListeners(getID(PARENT));
};
ReactEvent.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
ReactEvent.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, handleChildClick);
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
handleParentClick
);
ReactTestUtils.Simulate.click(CHILD);
expect(handleParentClick.mock.calls.length).toBe(1);
});
it('should not invoke newly inserted handlers while bubbling', function() {
var handleParentClick = mocks.getMockFunction();
var handleChildClick = function(abstractEvent) {
ReactEvent.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
var handleChildClick = function(event) {
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
handleParentClick
);
};
ReactEvent.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, handleChildClick);
ReactTestUtils.Simulate.click(CHILD);
expect(handleParentClick.mock.calls.length).toBe(0);
});
it('should infer onTouchTap from a touchStart/End', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -263,14 +278,14 @@ describe('ReactEvent', function() {
ReactTestUtils.nativeTouchData(0, 0)
);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
it('should infer onTouchTap from when dragging below threshold', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -281,14 +296,14 @@ describe('ReactEvent', function() {
ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1)
);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
it('should not onTouchTap from when dragging beyond threshold', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -303,20 +318,20 @@ describe('ReactEvent', function() {
it('should bubble onTouchTap', function() {
ReactEvent.putListener(
CHILD.id,
ReactEventEmitter.putListener(
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactEvent.putListener(
PARENT.id,
ReactEventEmitter.putListener(
getID(PARENT),
ON_TOUCH_TAP_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEvent.putListener(
GRANDPARENT.id,
ReactEventEmitter.putListener(
getID(GRANDPARENT),
ON_TOUCH_TAP_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -327,9 +342,9 @@ describe('ReactEvent', function() {
ReactTestUtils.nativeTouchData(0, 0)
);
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[1]).toBe(PARENT.id);
expect(idCallOrder[2]).toBe(GRANDPARENT.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
});
});

View File

@@ -0,0 +1,294 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @jsx React.DOM
* @emails react-core
*/
"use strict";
var React;
var ReactTestUtils;
var reactComponentExpect;
var ReactID;
describe('ReactIdentity', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
React = require('React');
ReactTestUtils = require('ReactTestUtils');
reactComponentExpect = require('reactComponentExpect');
ReactID = require('ReactID');
});
var idExp = /^\.r\[.+?\](.*)$/;
function checkId(child, expectedId) {
var actual = idExp.exec(ReactID.getID(child));
var expected = idExp.exec(expectedId);
expect(actual).toBeTruthy();
expect(expected).toBeTruthy();
expect(actual[1]).toEqual(expected[1]);
}
it('should allow keyed objects to express identity', function() {
var instance =
<div>
{{
first: <div />,
second: <div />
}}
</div>;
React.renderComponent(instance, document.createElement('div'));
var node = instance.getDOMNode();
reactComponentExpect(instance).toBeDOMComponentWithChildCount(2);
checkId(node.childNodes[0], '.r[0].{first}');
checkId(node.childNodes[1], '.r[0].{second}');
});
it('should allow key property to express identity', function() {
var instance =
<div>
<div key="apple" />
<div key="banana" />
<div key={0} />
<div key={123} />
</div>;
React.renderComponent(instance, document.createElement('div'));
var node = instance.getDOMNode();
reactComponentExpect(instance).toBeDOMComponentWithChildCount(4);
checkId(node.childNodes[0], '.r[0].[apple]');
checkId(node.childNodes[1], '.r[0].[banana]');
checkId(node.childNodes[2], '.r[0].[0]');
checkId(node.childNodes[3], '.r[0].[123]');
});
it('should use instance identity', function() {
var Wrapper = React.createClass({
render: function() {
return <a key="i_get_overwritten">{this.props.children}</a>;
}
});
var instance =
<div>
<Wrapper key="wrap1"><span key="squirrel" /></Wrapper>
<Wrapper key="wrap2"><span key="bunny" /></Wrapper>
<Wrapper><span key="chipmunk" /></Wrapper>
</div>;
React.renderComponent(instance, document.createElement('div'));
var node = instance.getDOMNode();
reactComponentExpect(instance).toBeDOMComponentWithChildCount(3);
checkId(node.childNodes[0], '.r[0].[wrap1]');
checkId(node.childNodes[0].firstChild, '.r[0].[wrap1].[squirrel]');
checkId(node.childNodes[1], '.r[0].[wrap2]');
checkId(node.childNodes[1].firstChild, '.r[0].[wrap2].[bunny]');
checkId(node.childNodes[2], '.r[0].[2]');
checkId(node.childNodes[2].firstChild, '.r[0].[2].[chipmunk]');
});
function renderAComponentWithKeyIntoContainer(key, container) {
var span1 = <span key={key} />;
var span2 = <span />;
var map = {};
map[key] = span2;
React.renderComponent(<div>{[span1, map]}</div>, container);
expect(span1.getDOMNode()).not.toBe(null);
expect(span2.getDOMNode()).not.toBe(null);
checkId(span1.getDOMNode(), '.r[0].[' + key + ']');
checkId(span2.getDOMNode(), '.r[0].[1]{' + key + '}');
}
it('should allow any character as a key, in a detached parent', function() {
var detachedContainer = document.createElement('div');
renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", detachedContainer);
});
it('should allow any character as a key, in an attached parent', function() {
// This test exists to protect against implementation details that
// incorrectly query escaped IDs using DOM tools like getElementById.
var attachedContainer = document.createElement('div');
document.body.appendChild(attachedContainer);
renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", attachedContainer);
document.body.removeChild(attachedContainer);
});
it('should not allow scripts in keys to execute', function() {
var h4x0rKey = '"><script>window.YOUVEBEENH4X0RED=true;</script><div id="';
var attachedContainer = document.createElement('div');
document.body.appendChild(attachedContainer);
renderAComponentWithKeyIntoContainer(h4x0rKey, attachedContainer);
document.body.removeChild(attachedContainer);
// If we get this far, make sure we haven't executed the code
expect(window.YOUVEBEENH4X0RED).toBe(undefined);
});
it('should let restructured components retain their uniqueness', function() {
var instance0 = <span />;
var instance1 = <span />;
var instance2 = <span />;
var TestComponent = React.createClass({
render: function() {
return (
<div>
{instance2}
{this.props.children[0]}
{this.props.children[1]}
</div>
);
}
});
var TestContainer = React.createClass({
render: function() {
return <TestComponent>{instance0} {instance1}</TestComponent>;
}
});
expect(function() {
React.renderComponent(<TestContainer />, document.createElement('div'));
}).not.toThrow();
});
it('should let nested restructures retain their uniqueness', function() {
var instance0 = <span />;
var instance1 = <span />;
var instance2 = <span />;
var TestComponent = React.createClass({
render: function() {
return (
<div>
{instance2}
{this.props.children[0]}
{this.props.children[1]}
</div>
);
}
});
var TestContainer = React.createClass({
render: function() {
return (
<div>
<TestComponent>{instance0} {instance1}</TestComponent>
</div>
);
}
});
expect(function() {
React.renderComponent(<TestContainer />, document.createElement('div'));
}).not.toThrow();
});
it('should let text nodes retain their uniqueness', function() {
var TestComponent = React.createClass({
render: function() {
return <div>{this.props.children}<span /></div>;
}
});
var TestContainer = React.createClass({
render: function() {
return (
<TestComponent>
<div />
{'second'}
</TestComponent>
);
}
});
expect(function() {
React.renderComponent(<TestContainer />, document.createElement('div'));
}).not.toThrow();
});
it('should retain key during updates in composite components', function() {
var TestComponent = React.createClass({
render: function() {
return <div>{this.props.children}</div>;
}
});
var TestContainer = React.createClass({
getInitialState: function() {
return { swapped: false };
},
swap: function() {
this.setState({ swapped: true });
},
render: function() {
return (
<TestComponent>
{this.state.swapped ? this.props.second : this.props.first}
{this.state.swapped ? this.props.first : this.props.second}
</TestComponent>
);
}
});
var instance0 = <span key="A" />;
var instance1 = <span key="B" />;
var wrapped = <TestContainer first={instance0} second={instance1} />;
React.renderComponent(wrapped, document.createElement('div'));
var beforeID = ReactID.getID(wrapped.getDOMNode().firstChild);
wrapped.swap();
var afterID = ReactID.getID(wrapped.getDOMNode().firstChild);
expect(beforeID).not.toEqual(afterID);
});
});

View File

@@ -19,15 +19,9 @@
"use strict";
require('mock-modules')
.dontMock('ReactInstanceHandles');
var React = require('React');
var ReactComponent = require('ReactComponent');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactTestUtils = require('ReactTestUtils');
var reactComponentExpect= require('reactComponentExpect');
var ReactID = require('ReactID');
/**
* Ensure that all callbacks are invoked, passing this unique argument.
@@ -64,251 +58,347 @@ function renderParentIntoDocument() {
return ReactTestUtils.renderIntoDocument(<ParentComponent />);
}
var aggregatedArgs = [];
function argAggregator(id, isUp, arg) {
aggregatedArgs.push({
id: id,
isUp: isUp,
arg: arg
});
}
describe('ReactInstanceHandles', function() {
var ReactInstanceHandles;
var nextDescendantID;
var traverseTwoPhase;
var traverseEnterLeave;
var getFirstCommonAncestorID;
var aggregatedArgs;
function argAggregator(id, isUp, arg) {
aggregatedArgs.push({
id: id,
isUp: isUp,
arg: arg
});
}
describe('ReactInstanceHandles traversal', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
aggregatedArgs = [];
ReactInstanceHandles = require('ReactInstanceHandles');
nextDescendantID = ReactInstanceHandles.nextDescendantID;
getFirstCommonAncestorID = ReactInstanceHandles.getFirstCommonAncestorID;
traverseTwoPhase = ReactInstanceHandles.traverseTwoPhase;
traverseEnterLeave = ReactInstanceHandles.traverseEnterLeave;
aggregatedArgs = [];
});
it("should return next descendent from window", function() {
var parent = renderParentIntoDocument();
expect(nextDescendantID('', parent.refs.P_P1._rootNodeID)).toBe(
parent.refs.P._rootNodeID
);
describe('isRenderedByReact', function() {
it('should not crash on text nodes', function() {
expect(function() {
ReactInstanceHandles.isRenderedByReact(document.createTextNode('yolo'));
}).not.toThrow();
});
});
it("should return window for next descendent towards window", function() {
expect(nextDescendantID('', '')).toBe('');
});
describe('findComponentRoot', function() {
it('should find the correct node with prefix sibling IDs', function() {
var parentNode = document.createElement('div');
var childNodeA = document.createElement('div');
var childNodeB = document.createElement('div');
parentNode.appendChild(childNodeA);
parentNode.appendChild(childNodeB);
it("should return self for next descendent towards self", function() {
var parent = renderParentIntoDocument();
expect(
nextDescendantID(
parent.refs.P_P1._rootNodeID,
parent.refs.P_P1._rootNodeID
)
).toBe(parent.refs.P_P1._rootNodeID);
});
ReactID.setID(parentNode, '.react[0]');
ReactID.setID(childNodeA, '.react[0].0');
ReactID.setID(childNodeB, '.react[0].0:1');
it("should not traverse when traversing outside DOM", function() {
var targetID = '';
var expectedAggregation = [];
traverseTwoPhase(targetID, argAggregator, ARG);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
expect(
ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB)
)
).toBe(childNodeB);
});
it("should not traverse when enter/leaving outside DOM", function() {
var targetID = '';
var expectedAggregation = [];
traverseEnterLeave(targetID, targetID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it('should work around unidentified nodes', function() {
var parentNode = document.createElement('div');
var childNodeA = document.createElement('div');
var childNodeB = document.createElement('div');
parentNode.appendChild(childNodeA);
parentNode.appendChild(childNodeB);
it("should not traverse if enter/leave the same node", function() {
var parent = renderParentIntoDocument();
var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var enterID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var expectedAggregation = [];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
ReactID.setID(parentNode, '.react[0]');
// No ID on `childNodeA`.
ReactID.setID(childNodeB, '.react[0].0:1');
it("should traverse two phase across component boundary", function() {
var parent = renderParentIntoDocument();
var targetID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: false, arg: ARG},
expect(
ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB)
)
).toBe(childNodeB);
});
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
traverseTwoPhase(targetID, argAggregator, ARG);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it('should throw if a rendered element cannot be found', function() {
var parentNode = document.createElement('table');
var childNodeA = document.createElement('tbody');
var childNodeB = document.createElement('tr');
parentNode.appendChild(childNodeA);
childNodeA.appendChild(childNodeB);
it("should traverse two phase at shallowest node", function() {
var parent = renderParentIntoDocument();
var targetID = parent.refs.P._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
traverseTwoPhase(targetID, argAggregator, ARG);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
ReactID.setID(parentNode, '.react[0]');
// No ID on `childNodeA`, it was "rendered by the browser".
ReactID.setID(childNodeB, '.react[0].1:0');
it("should traverse enter/leave to sibling - avoids parent", function() {
var parent = renderParentIntoDocument();
var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var enterID = parent.refs.P_P1_C1.refs.DIV_2._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
// enter/leave shouldn't fire antyhing on the parent
{id: parent.refs.P_P1_C1.refs.DIV_2._rootNodeID, isUp: false, arg: ARG2}
];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
expect(ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB)
)).toBe(childNodeB);
it("should traverse enter/leave to parent - avoids parent", function() {
var parent = renderParentIntoDocument();
var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}
];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should enter from the window", function() {
var parent = renderParentIntoDocument();
var leaveID = ''; // From the window or outside of the React sandbox.
var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2},
{id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG2},
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG2}
];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should enter from the window to the shallowest", function() {
var parent = renderParentIntoDocument();
var leaveID = ''; // From the window or outside of the React sandbox.
var enterID = parent.refs.P._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2}
];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should leave to the window", function() {
var parent = renderParentIntoDocument();
var enterID = ''; // From the window or outside of the React sandbox.
var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should leave to the window from the shallowest", function() {
var parent = renderParentIntoDocument();
var enterID = ''; // From the window or outside of the React sandbox.
var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should determine the first common ancestor correctly", function() {
var parent = renderParentIntoDocument();
var ancestors = [
// Common ancestor from window to deep element is ''.
{ one: {_rootNodeID: ''},
two: parent.refs.P_P1_C1.refs.DIV_1,
com: {_rootNodeID: ''}
},
// Same as previous - reversed direction.
{ one: parent.refs.P_P1_C1.refs.DIV_1,
two: {_rootNodeID: ''},
com: {_rootNodeID: ''}
},
// Common ancestor from window to shallow id is ''.
{ one: parent.refs.P,
two: {_rootNodeID: ''},
com: {_rootNodeID: ''}
},
// Common ancestor with self is self.
{ one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV_1,
com: parent.refs.P_P1_C1.refs.DIV_1
},
// Common ancestor with self is self - even if topmost DOM.
{ one: parent.refs.P, two: parent.refs.P, com: parent.refs.P },
// Siblings
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV_2,
com: parent.refs.P_P1_C1.refs.DIV
},
// Common ancestor with parent is the parent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV,
com: parent.refs.P_P1_C1.refs.DIV
},
// Common ancestor with grandparent is the grandparent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1,
com: parent.refs.P_P1_C1
},
// Grantparent across subcomponent boundaries.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C2.refs.DIV_1,
com: parent.refs.P_P1
},
// Something deep with something one-off.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_OneOff,
com: parent.refs.P
}
];
var i;
for (i = 0; i < ancestors.length; i++) {
var plan = ancestors[i];
var firstCommon = getFirstCommonAncestorID(
plan.one._rootNodeID,
plan.two._rootNodeID
expect(function() {
ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB) + ":junk"
);
}).toThrow(
'Invariant Violation: findComponentRoot(..., .react[0].1:0:junk): ' +
'Unable to find element. This probably means the DOM was ' +
'unexpectedly mutated (e.g. by the browser).'
);
expect(firstCommon).toBe(plan.com._rootNodeID);
}
});
});
});
describe('ReactInstanceHandles.getReactRootIDFromNodeID', function() {
it('should support strings', function() {
var test = '.reactRoot[s_0_1][0]..[1]';
var expected = '.reactRoot[s_0_1]';
var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test);
expect(actual).toEqual(expected);
describe('getReactRootIDFromNodeID', function() {
it('should support strings', function() {
var test = '.r[s_0_1][0]..[1]';
var expected = '.r[s_0_1]';
var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test);
expect(actual).toEqual(expected);
});
});
describe('traverseTwoPhase', function() {
it("should not traverse when traversing outside DOM", function() {
var targetID = '';
var expectedAggregation = [];
ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should traverse two phase across component boundary", function() {
var parent = renderParentIntoDocument();
var targetID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should traverse two phase at shallowest node", function() {
var parent = renderParentIntoDocument();
var targetID = parent.refs.P._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
});
describe('traverseEnterLeave', function() {
it("should not traverse when enter/leaving outside DOM", function() {
var targetID = '';
var expectedAggregation = [];
ReactInstanceHandles.traverseEnterLeave(
targetID, targetID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should not traverse if enter/leave the same node", function() {
var parent = renderParentIntoDocument();
var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var enterID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var expectedAggregation = [];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should traverse enter/leave to sibling - avoids parent", function() {
var parent = renderParentIntoDocument();
var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var enterID = parent.refs.P_P1_C1.refs.DIV_2._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
// enter/leave shouldn't fire antyhing on the parent
{id: parent.refs.P_P1_C1.refs.DIV_2._rootNodeID, isUp: false, arg: ARG2}
];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should traverse enter/leave to parent - avoids parent", function() {
var parent = renderParentIntoDocument();
var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}
];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should enter from the window", function() {
var parent = renderParentIntoDocument();
var leaveID = ''; // From the window or outside of the React sandbox.
var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2},
{id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG2},
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG2}
];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should enter from the window to the shallowest", function() {
var parent = renderParentIntoDocument();
var leaveID = ''; // From the window or outside of the React sandbox.
var enterID = parent.refs.P._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2}
];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should leave to the window", function() {
var parent = renderParentIntoDocument();
var enterID = ''; // From the window or outside of the React sandbox.
var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
it("should leave to the window from the shallowest", function() {
var parent = renderParentIntoDocument();
var enterID = ''; // From the window or outside of the React sandbox.
var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
var expectedAggregation = [
{id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
{id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
];
ReactInstanceHandles.traverseEnterLeave(
leaveID, enterID, argAggregator, ARG, ARG2
);
expect(aggregatedArgs).toEqual(expectedAggregation);
});
});
describe('getNextDescendantID', function() {
it("should return next descendent from window", function() {
var parent = renderParentIntoDocument();
expect(
ReactInstanceHandles._getNextDescendantID(
'',
parent.refs.P_P1._rootNodeID
)
).toBe(parent.refs.P._rootNodeID);
});
it("should return window for next descendent towards window", function() {
expect(ReactInstanceHandles._getNextDescendantID('', '')).toBe('');
});
it("should return self for next descendent towards self", function() {
var parent = renderParentIntoDocument();
expect(
ReactInstanceHandles._getNextDescendantID(
parent.refs.P_P1._rootNodeID,
parent.refs.P_P1._rootNodeID
)
).toBe(parent.refs.P_P1._rootNodeID);
});
});
describe('getFirstCommonAncestorID', function() {
it("should determine the first common ancestor correctly", function() {
var parent = renderParentIntoDocument();
var ancestors = [
// Common ancestor from window to deep element is ''.
{ one: {_rootNodeID: ''},
two: parent.refs.P_P1_C1.refs.DIV_1,
com: {_rootNodeID: ''}
},
// Same as previous - reversed direction.
{ one: parent.refs.P_P1_C1.refs.DIV_1,
two: {_rootNodeID: ''},
com: {_rootNodeID: ''}
},
// Common ancestor from window to shallow id is ''.
{ one: parent.refs.P,
two: {_rootNodeID: ''},
com: {_rootNodeID: ''}
},
// Common ancestor with self is self.
{ one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV_1,
com: parent.refs.P_P1_C1.refs.DIV_1
},
// Common ancestor with self is self - even if topmost DOM.
{ one: parent.refs.P, two: parent.refs.P, com: parent.refs.P },
// Siblings
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV_2,
com: parent.refs.P_P1_C1.refs.DIV
},
// Common ancestor with parent is the parent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1.refs.DIV,
com: parent.refs.P_P1_C1.refs.DIV
},
// Common ancestor with grandparent is the grandparent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1,
com: parent.refs.P_P1_C1
},
// Grantparent across subcomponent boundaries.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C2.refs.DIV_1,
com: parent.refs.P_P1
},
// Something deep with something one-off.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_OneOff,
com: parent.refs.P
}
];
var i;
for (i = 0; i < ancestors.length; i++) {
var plan = ancestors[i];
var firstCommon = ReactInstanceHandles._getFirstCommonAncestorID(
plan.one._rootNodeID,
plan.two._rootNodeID
);
expect(firstCommon).toBe(plan.com._rootNodeID);
}
});
});
});

View File

@@ -1,4 +1,18 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @jsx React.DOM
* @emails react-core
*/

View File

@@ -19,12 +19,7 @@
"use strict";
var ReactMount = require('ReactMount');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var ReactMount = require('ReactMount');
var reactComponentExpect = require('reactComponentExpect');
describe('ReactMount', function() {
it("should destroy a react root upon request", function() {

View File

@@ -23,6 +23,7 @@ require('mock-modules');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var ReactID = require('ReactID');
var objMapKeyVal = require('objMapKeyVal');
@@ -41,9 +42,8 @@ var stripEmptyValues = function(obj) {
};
/**
* Children are currently named '{<providedKey>}' so we can retrieve the
* originally provided key by stripping out the braces. This relies on a tiny
* implementation detail of the rendering system.
* Child names is are wrapped in an prefix and suffix character. We strip those
* out. This relies on a tiny implementation detail of the rendering system.
*/
var getOriginalKey = function(childName) {
return childName.substr(1, childName.length - 2);
@@ -191,7 +191,7 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
var i;
var orderedDomIds = [];
for (i=0; i < statusDisplayNodes.length; i++) {
orderedDomIds.push(statusDisplayNodes[i].id);
orderedDomIds.push(ReactID.getID(statusDisplayNodes[i]));
}
var orderedLogicalIds = [];

View File

@@ -17,13 +17,14 @@
* @emails react-core
*/
/*jslint evil: true */
"use strict";
require('mock-modules');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var ReactTextComponent = require('ReactTextComponent');
var reactComponentExpect = require('reactComponentExpect');
@@ -70,10 +71,6 @@ var assertSingleChild = function(instance, text) {
};
// Helpers
var renderSingleContentChild = function(text) {
var d = ReactTestUtils.renderIntoDocument(<div content={text} />);
return d;
};
var renderSingleTextChild = function(text) {
var d = ReactTestUtils.renderIntoDocument(<div>{text}</div>);
return d;
@@ -141,7 +138,7 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render zero string as text node then switch to spans', function() {
it('should render zero string as text node then switch to spans', function() {
var d = renderSingleTextChild('0');
// false should act exactly as a null child
assertNodeText(d, '0');
@@ -149,7 +146,7 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render zero number as text node then switch to spans', function() {
it('should render zero number as text node then switch to spans', function() {
var d = renderSingleTextChild('0');
// false should act exactly as a null child
assertNodeText(d, 0);
@@ -157,11 +154,6 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render content to single text node', function() {
var d = renderSingleContentChild('hello');
assertNodeText(d, 'hello');
});
it('should render a single text child to a single text node', function() {
var d = renderSingleTextChild('hello');
assertNodeText(d, 'hello');
@@ -233,12 +225,6 @@ describe('ReactMultiChildText', function() {
assertNodeText(d, '0');
});
it('should render content number zero as text node', function() {
var d = renderSingleContentChild(0);
// false should act exactly as a null child
assertNodeText(d, '0');
});
it('should render zero string as string child', function() {
var d = renderMultipleTextChildren('0', 234.2);
// false should act exactly as a null child
@@ -249,7 +235,7 @@ describe('ReactMultiChildText', function() {
var d = renderMultipleTextChildren('0', 234.2);
// false should act exactly as a null child
assertMultiChild(d, '0', '234.2');
d.replaceProps({content: '0'});
d.replaceProps({children: '0'});
assertNodeText(d, '0');
});
@@ -257,7 +243,7 @@ describe('ReactMultiChildText', function() {
var d = renderMultipleTextChildren(0, 234.2);
// false should act exactly as a null child
assertMultiChild(d, '0', '234.2');
d.replaceProps({content: 0});
d.replaceProps({children: 0});
// BELOW REVEALS A BUG IN JSDOM
// assertNodeText(d, '0'); // This works in the browser.
});
@@ -265,7 +251,7 @@ describe('ReactMultiChildText', function() {
it('should render multiple children then switch to inline', function() {
var d = renderMultipleTextChildren('hello', 'goodbye');
assertMultiChild(d, 'hello', 'goodbye');
d.replaceProps({content: 'hello'});
d.replaceProps({children: 'hello'});
assertNodeText(d, 'hello');
});
@@ -285,13 +271,6 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render content, then switch to text components ', function() {
var d = renderSingleContentChild('hello');
assertNodeText(d, 'hello');
d.replaceProps({children: ['hello', 'goodbye']});
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render inline child, then switch to composite', function() {
var d = renderSingleTextChild('hello');
assertNodeText(d, 'hello');
@@ -301,9 +280,11 @@ describe('ReactMultiChildText', function() {
.toBeCompositeComponentWithType(TestCompositeComponent);
});
it('should throw if rendering both content and children', function() {
it('should throw if rendering both HTML and children', function() {
expect(function() {
ReactTestUtils.renderIntoDocument(<div content="asdf">ghjkl</div>);
ReactTestUtils.renderIntoDocument(
<div dangerouslySetInnerHTML={{_html: 'abcdef'}}>ghjkl</div>
);
}).toThrow();
});
});

View File

@@ -21,6 +21,8 @@
"use strict";
var mocks = require('mocks');
describe('ReactNativeComponent', function() {
describe('updateDOM', function() {
@@ -68,7 +70,7 @@ describe('ReactNativeComponent', function() {
});
it("should update styles when mutating style object", function() {
var styles = { display: 'none', fontFamily: 'Arial' };
var styles = { display: 'none', fontFamily: 'Arial', opacity: 0 };
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
var stubStyle = stub.getDOMNode().style;
@@ -80,12 +82,21 @@ describe('ReactNativeComponent', function() {
stub.receiveProps({ style: styles }, transaction);
expect(stubStyle.display).toEqual('block');
expect(stubStyle.fontFamily).toEqual('Arial');
expect(stubStyle.opacity).toEqual('0');
styles.fontFamily = 'Helvetica';
stub.receiveProps({ style: styles }, transaction);
expect(stubStyle.display).toEqual('block');
expect(stubStyle.fontFamily).toEqual('Helvetica');
expect(stubStyle.opacity).toEqual('0');
styles.opacity = 0.5;
stub.receiveProps({ style: styles }, transaction);
expect(stubStyle.display).toEqual('block');
expect(stubStyle.fontFamily).toEqual('Helvetica');
expect(stubStyle.opacity).toEqual('0.5');
});
it("should update styles if initially null", function() {
@@ -100,6 +111,76 @@ describe('ReactNativeComponent', function() {
expect(stubStyle.display).toEqual('block');
});
it("should remove attributes", function() {
var stub = ReactTestUtils.renderIntoDocument(<img height='17' />);
expect(stub.getDOMNode().hasAttribute('height')).toBe(true);
stub.receiveProps({}, transaction);
expect(stub.getDOMNode().hasAttribute('height')).toBe(false);
});
it("should remove properties", function() {
var stub = ReactTestUtils.renderIntoDocument(<div className='monkey' />);
expect(stub.getDOMNode().className).toEqual('monkey');
stub.receiveProps({}, transaction);
expect(stub.getDOMNode().className).toEqual('');
});
it("should clear a single style prop when changing 'style'", function() {
var styles = {display: 'none', color: 'red'};
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
var stubStyle = stub.getDOMNode().style;
styles = {color: 'green'};
stub.receiveProps({ style: styles }, transaction);
expect(stubStyle.display).toEqual('');
expect(stubStyle.color).toEqual('green');
});
it("should clear all the styles when removing 'style'", function() {
var styles = {display: 'none', color: 'red'};
var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
var stubStyle = stub.getDOMNode().style;
stub.receiveProps({}, transaction);
expect(stubStyle.display).toEqual('');
expect(stubStyle.color).toEqual('');
});
it("should empty element when removing innerHTML", function() {
var stub = ReactTestUtils.renderIntoDocument(
<div dangerouslySetInnerHTML={{__html: ":)"}} />
);
expect(stub.getDOMNode().innerHTML).toEqual(':)');
stub.receiveProps({}, transaction);
expect(stub.getDOMNode().innerHTML).toEqual('');
});
it("should not incur unnecessary DOM mutations", function() {
var stub = ReactTestUtils.renderIntoDocument(<div value="" />);
var node = stub.getDOMNode();
var nodeValue = node.value;
var nodeValueSetter = mocks.getMockFunction();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
},
set: nodeValueSetter.mockImplementation(function(newValue) {
nodeValue = newValue;
})
});
stub.receiveProps({value: ''}, transaction);
expect(nodeValueSetter.mock.calls.length).toBe(0);
stub.receiveProps({}, transaction);
expect(nodeValueSetter.mock.calls.length).toBe(1);
});
});
describe('createOpenTagMarkup', function() {
@@ -112,6 +193,9 @@ describe('ReactNativeComponent', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
var ReactDefaultInjection = require('ReactDefaultInjection');
ReactDefaultInjection.inject();
var mixInto = require('mixInto');
var ReactNativeComponent = require('ReactNativeComponent');
@@ -136,7 +220,7 @@ describe('ReactNativeComponent', function() {
});
});
it("should handle className", function() {
it("should generate the correct markup with className", function() {
expect(genMarkup({ className: 'a' })).toHaveAttribute('class', 'a');
expect(genMarkup({ className: 'a b' })).toHaveAttribute('class', 'a b');
expect(genMarkup({ className: '' })).toHaveAttribute('class', '');
@@ -192,40 +276,30 @@ describe('ReactNativeComponent', function() {
var mixInto = require('mixInto');
var ReactComponent = require('ReactComponent');
var ReactMultiChild = require('ReactMultiChild');
var ReactNativeComponent = require('ReactNativeComponent');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
var NodeStub = function(initialProps) {
var StubNativeComponent = function(initialProps) {
ReactComponent.Mixin.construct.call(this, initialProps);
};
mixInto(NodeStub, ReactNativeComponent.Mixin);
mixInto(StubNativeComponent, ReactComponent.Mixin);
mixInto(StubNativeComponent, ReactNativeComponent.Mixin);
mixInto(StubNativeComponent, ReactMultiChild.Mixin);
mountComponent = function(props) {
var transaction = new ReactReconcileTransaction();
return (new NodeStub(props)).mountComponent('test', transaction);
var stubComponent = new StubNativeComponent(props);
return stubComponent.mountComponent('test', transaction);
};
});
it("should validate against multiple children props", function() {
expect(function() {
mountComponent({ content: '', children: '' });
}).toThrow(
'Invariant Violation: Can only set one of `children`, ' +
'`props.content`, or `props.dangerouslySetInnerHTML`.'
);
expect(function() {
mountComponent({ content: '', dangerouslySetInnerHTML: '' });
}).toThrow(
'Invariant Violation: Can only set one of `children`, ' +
'`props.content`, or `props.dangerouslySetInnerHTML`.'
);
expect(function() {
mountComponent({ children: '', dangerouslySetInnerHTML: '' });
}).toThrow(
'Invariant Violation: Can only set one of `children`, ' +
'`props.content`, or `props.dangerouslySetInnerHTML`.'
'Invariant Violation: Can only set one of `children` or ' +
'`props.dangerouslySetInnerHTML`.'
);
});
@@ -239,4 +313,31 @@ describe('ReactNativeComponent', function() {
});
});
describe('unmountComponent', function() {
it("should clean up listeners", function() {
var React = require('React');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactID = require('ReactID');
var container = document.createElement('div');
document.documentElement.appendChild(container);
var callback = function() {};
var instance = <div onClick={callback} />;
React.renderComponent(instance, container);
var rootNode = instance.getDOMNode();
var rootNodeID = ReactID.getID(rootNode);
expect(
ReactEventEmitter.getListener(rootNodeID, 'onClick')
).toBe(callback);
React.unmountAndReleaseReactRootNode(container);
expect(
ReactEventEmitter.getListener(rootNodeID, 'onClick')
).toBe(undefined);
});
});
});

View File

@@ -19,14 +19,14 @@
"use strict";
var Props = require('ReactProps');
var Props = require('ReactPropTypes');
function typeCheck(declaration, value) {
var props = {};
if (arguments.length > 1) {
props.testProp = value;
}
return declaration.bind(this, props, 'testProp', 'testComponent');
return declaration.bind(null, props, 'testProp', 'testComponent');
}
describe('Primitive Types', function() {
@@ -56,6 +56,7 @@ describe('Primitive Types', function() {
it("should not throw for valid values", function() {
expect(typeCheck(Props.array, [])).not.toThrow();
expect(typeCheck(Props.bool, false)).not.toThrow();
expect(typeCheck(Props.func, function() {})).not.toThrow();
expect(typeCheck(Props.number, 0)).not.toThrow();
expect(typeCheck(Props.object, {})).not.toThrow();
expect(typeCheck(Props.string, '')).not.toThrow();

View File

@@ -0,0 +1,295 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @jsx React.DOM
* @emails react-core
*/
"use strict";
var React;
var ReactTestUtils;
var ReactUpdates;
describe('ReactUpdates', function() {
beforeEach(function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
ReactUpdates = require('ReactUpdates');
});
it('should batch state when updating state twice', function() {
var updateCount = 0;
var Component = React.createClass({
getInitialState: function() {
return {x: 0};
},
componentDidUpdate: function() {
updateCount++;
},
render: function() {
return <div>{this.state.x}</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.state.x).toBe(0);
ReactUpdates.batchedUpdates(function() {
instance.setState({x: 1});
instance.setState({x: 2});
expect(instance.state.x).toBe(0);
expect(updateCount).toBe(0);
});
expect(instance.state.x).toBe(2);
expect(updateCount).toBe(1);
});
it('should batch state when updating two different state keys', function() {
var updateCount = 0;
var Component = React.createClass({
getInitialState: function() {
return {x: 0, y: 0};
},
componentDidUpdate: function() {
updateCount++;
},
render: function() {
return <div>({this.state.x}, {this.state.y})</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.state.x).toBe(0);
expect(instance.state.y).toBe(0);
ReactUpdates.batchedUpdates(function() {
instance.setState({x: 1});
instance.setState({y: 2});
expect(instance.state.x).toBe(0);
expect(instance.state.y).toBe(0);
expect(updateCount).toBe(0);
});
expect(instance.state.x).toBe(1);
expect(instance.state.y).toBe(2);
expect(updateCount).toBe(1);
});
it('should batch state and props together', function() {
var updateCount = 0;
var Component = React.createClass({
getInitialState: function() {
return {y: 0};
},
componentDidUpdate: function() {
updateCount++;
},
render: function() {
return <div>({this.props.x}, {this.state.y})</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Component x={0} />);
expect(instance.props.x).toBe(0);
expect(instance.state.y).toBe(0);
ReactUpdates.batchedUpdates(function() {
instance.setProps({x: 1});
instance.setState({y: 2});
expect(instance.props.x).toBe(0);
expect(instance.state.y).toBe(0);
expect(updateCount).toBe(0);
});
expect(instance.props.x).toBe(1);
expect(instance.state.y).toBe(2);
expect(updateCount).toBe(1);
});
it('should batch parent/child state updates together', function() {
var parentUpdateCount = 0;
var Parent = React.createClass({
getInitialState: function() {
return {x: 0};
},
componentDidUpdate: function() {
parentUpdateCount++;
},
render: function() {
return <div><Child ref="child" x={this.state.x} /></div>;
}
});
var childUpdateCount = 0;
var Child = React.createClass({
getInitialState: function() {
return {y: 0};
},
componentDidUpdate: function() {
childUpdateCount++;
},
render: function() {
return <div>{this.props.x + this.state.y}</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Parent />);
var child = instance.refs.child;
expect(instance.state.x).toBe(0);
expect(child.state.y).toBe(0);
ReactUpdates.batchedUpdates(function() {
instance.setState({x: 1});
child.setState({y: 2});
expect(instance.state.x).toBe(0);
expect(child.state.y).toBe(0);
expect(parentUpdateCount).toBe(0);
expect(childUpdateCount).toBe(0);
});
expect(instance.state.x).toBe(1);
expect(child.state.y).toBe(2);
expect(parentUpdateCount).toBe(1);
expect(childUpdateCount).toBe(1);
});
it('should batch child/parent state updates together', function() {
var parentUpdateCount = 0;
var Parent = React.createClass({
getInitialState: function() {
return {x: 0};
},
componentDidUpdate: function() {
parentUpdateCount++;
},
render: function() {
return <div><Child ref="child" x={this.state.x} /></div>;
}
});
var childUpdateCount = 0;
var Child = React.createClass({
getInitialState: function() {
return {y: 0};
},
componentDidUpdate: function() {
childUpdateCount++;
},
render: function() {
return <div>{this.props.x + this.state.y}</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Parent />);
var child = instance.refs.child;
expect(instance.state.x).toBe(0);
expect(child.state.y).toBe(0);
ReactUpdates.batchedUpdates(function() {
child.setState({y: 2});
instance.setState({x: 1});
expect(instance.state.x).toBe(0);
expect(child.state.y).toBe(0);
expect(parentUpdateCount).toBe(0);
expect(childUpdateCount).toBe(0);
});
expect(instance.state.x).toBe(1);
expect(child.state.y).toBe(2);
expect(parentUpdateCount).toBe(1);
// When we update the child first, we currently incur two updates because
// we aren't smart about what order to process the components in.
// TODO: Reduce the update count here to 1
expect(childUpdateCount).toBe(2);
});
it('should support chained state updates', function() {
var updateCount = 0;
var Component = React.createClass({
getInitialState: function() {
return {x: 0};
},
componentDidUpdate: function() {
updateCount++;
},
render: function() {
return <div>{this.state.x}</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.state.x).toBe(0);
var innerCallbackRun = false;
ReactUpdates.batchedUpdates(function() {
instance.setState({x: 1}, function() {
instance.setState({x: 2}, function() {
innerCallbackRun = true;
expect(instance.state.x).toBe(2);
expect(updateCount).toBe(2);
});
expect(instance.state.x).toBe(1);
expect(updateCount).toBe(1);
});
expect(instance.state.x).toBe(0);
expect(updateCount).toBe(0);
});
expect(innerCallbackRun).toBeTruthy();
expect(instance.state.x).toBe(2);
expect(updateCount).toBe(2);
});
it('should batch forceUpdate together', function() {
var shouldUpdateCount = 0;
var updateCount = 0;
var Component = React.createClass({
getInitialState: function() {
return {x: 0};
},
shouldComponentUpdate: function() {
shouldUpdateCount++;
},
componentDidUpdate: function() {
updateCount++;
},
render: function() {
return <div>{this.state.x}</div>;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.state.x).toBe(0);
var callbacksRun = 0;
ReactUpdates.batchedUpdates(function() {
instance.setState({x: 1}, function() {
callbacksRun++;
});
instance.forceUpdate(function() {
callbacksRun++;
});
expect(instance.state.x).toBe(0);
expect(updateCount).toBe(0);
});
expect(callbacksRun).toBe(2);
// shouldComponentUpdate shouldn't be called since we're forcing
expect(shouldUpdateCount).toBe(0);
expect(instance.state.x).toBe(1);
expect(updateCount).toBe(1);
});
});

View File

@@ -36,6 +36,9 @@ var ClickCounter = React.createClass({
triggerReset: function() {
this.setState({count: this.props.initialCount});
},
handleClick: function() {
this.setState({count: this.state.count + 1});
},
render: function() {
var children = [];
var i;
@@ -45,8 +48,7 @@ var ClickCounter = React.createClass({
);
}
return (
<span className="clickIncrementer"
onClick={this.setState.bind(this, {count: this.state.count + 1})}>
<span className="clickIncrementer" onClick={this.handleClick}>
{children}
</span>
);
@@ -75,7 +77,7 @@ var TestRefsComponent = React.createClass({
render: function() {
return (
<div>
<div ref="resetDiv" onClick={this.doReset.bind(this)}>
<div ref="resetDiv" onClick={this.doReset}>
Reset Me By Clicking This.
</div>
<GeneralContainerComponent ref="myContainer">

89
src/dom/CSSProperty.js Normal file
View File

@@ -0,0 +1,89 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule CSSProperty
*/
"use strict";
/**
* CSS properties which accept numbers but are not in units of "px".
*/
var isUnitlessNumber = {
fillOpacity: true,
fontWeight: true,
opacity: true,
orphans: true,
zIndex: true,
zoom: true
};
/**
* Most style properties can be unset by doing .style[prop] = '' but IE8
* doesn't like doing that with shorthand properties so for the properties that
* IE8 breaks on, which are listed here, we instead unset each of the
* individual properties. See http://bugs.jquery.com/ticket/12385.
* The 4-value 'clock' properties like margin, padding, border-width seem to
* behave without any problems. Curiously, list-style works too without any
* special prodding.
*/
var shorthandPropertyExpansions = {
background: {
backgroundImage: true,
backgroundPosition: true,
backgroundRepeat: true,
backgroundColor: true
},
border: {
borderWidth: true,
borderStyle: true,
borderColor: true
},
borderBottom: {
borderBottomWidth: true,
borderBottomStyle: true,
borderBottomColor: true
},
borderLeft: {
borderLeftWidth: true,
borderLeftStyle: true,
borderLeftColor: true
},
borderRight: {
borderRightWidth: true,
borderRightStyle: true,
borderRightColor: true
},
borderTop: {
borderTopWidth: true,
borderTopStyle: true,
borderTopColor: true
},
font: {
fontStyle: true,
fontVariant: true,
fontWeight: true,
fontSize: true,
lineHeight: true,
fontFamily: true
}
};
var CSSProperty = {
isUnitlessNumber: isUnitlessNumber,
shorthandPropertyExpansions: shorthandPropertyExpansions
};
module.exports = CSSProperty;

View File

@@ -14,11 +14,13 @@
* limitations under the License.
*
* @providesModule CSSPropertyOperations
* @typechecks
* @typechecks static-only
*/
"use strict";
var CSSProperty = require('CSSProperty');
var dangerousStyleValue = require('dangerousStyleValue');
var escapeTextForBrowser = require('escapeTextForBrowser');
var hyphenate = require('hyphenate');
@@ -42,7 +44,7 @@ var CSSPropertyOperations = {
* Undefined values are ignored so that declarative programming is easier.
*
* @param {object} styles
* @return {string}
* @return {?string}
*/
createMarkupForStyles: function(styles) {
var serialized = '';
@@ -51,16 +53,17 @@ var CSSPropertyOperations = {
continue;
}
var styleValue = styles[styleName];
if (typeof styleValue !== 'undefined') {
if (styleValue != null) {
serialized += processStyleName(styleName) + ':';
serialized += dangerousStyleValue(styleName, styleValue) + ';';
}
}
return serialized;
return serialized || null;
},
/**
* Sets the value for multiple styles on a node.
* Sets the value for multiple styles on a node. If a value is specified as
* '' (empty string), the corresponding style property will be unset.
*
* @param {DOMElement} node
* @param {object} styles
@@ -71,8 +74,21 @@ var CSSPropertyOperations = {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
var styleValue = styles[styleName];
style[styleName] = dangerousStyleValue(styleName, styleValue);
var styleValue = dangerousStyleValue(styleName, styles[styleName]);
if (styleValue) {
style[styleName] = styleValue;
} else {
var expansion = CSSProperty.shorthandPropertyExpansions[styleName];
if (expansion) {
// Shorthand property that IE8 won't like unsetting, so unset each
// component to placate it
for (var individualStyleName in expansion) {
style[individualStyleName] = '';
}
} else {
style[styleName] = '';
}
}
}
}

View File

@@ -16,6 +16,9 @@
* @providesModule DOMChildrenOperations
*/
// Empty blocks improve readability so disable that warning
// jshint -W035
"use strict";
var Danger = require('Danger');
@@ -44,7 +47,7 @@ var REMOVE_AT = keyOf({removeAt: null});
*
* @param {Element} parent Parent DOM node.
* @param {Object} childOperations Description of child operations.
* @returns {Array?} Sparse array containing elements by their current index in
* @return {Array?} Sparse array containing elements by their current index in
* the DOM.
*/
var _getNodesByOriginalIndex = function(parent, childOperations) {
@@ -129,18 +132,13 @@ var manageChildren = function(parent, childOperations) {
_placeNodesAtDestination(parent, childOperations, nodesByOriginalIndex);
};
var setTextNodeValueAtIndex = function(parent, index, val) {
parent.childNodes[index].nodeValue = val;
};
/**
* Also reexport all of the dangerous functions. It helps to have all dangerous
* functions located in a single module `Danger`.
*/
var DOMChildrenOperations = {
dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
manageChildren: manageChildren,
setTextNodeValueAtIndex: setTextNodeValueAtIndex
manageChildren: manageChildren
};
module.exports = DOMChildrenOperations;

233
src/dom/DOMProperty.js Normal file
View File

@@ -0,0 +1,233 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule DOMProperty
* @typechecks static-only
*/
/*jslint bitwise: true */
"use strict";
var invariant = require('invariant');
var DOMPropertyInjection = {
/**
* Mapping from normalized, camelcased property names to a configuration that
* specifies how the associated DOM property should be accessed or rendered.
*/
MUST_USE_ATTRIBUTE: 0x1,
MUST_USE_PROPERTY: 0x2,
HAS_BOOLEAN_VALUE: 0x4,
HAS_SIDE_EFFECTS: 0x8,
/**
* Inject some specialized knowledge about the DOM. This takes a config object
* with the following properties:
*
* isCustomAttribute: function that given an attribute name will return true
* if it can be inserted into the DOM verbatim. Useful for data-* or aria-*
* attributes where it's impossible to enumerate all of the possible
* attribute names,
*
* Properties: object mapping DOM property name to one of the
* DOMPropertyInjection constants or null. If your attribute isn't in here,
* it won't get written to the DOM.
*
* DOMAttributeNames: object mapping React attribute name to the DOM
* attribute name. Attribute names not specified use the **lowercase**
* normalized name.
*
* DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
* Property names not specified use the normalized name.
*
* DOMMutationMethods: Properties that require special mutation methods. If
* `value` is undefined, the mutation method should unset the property.
*
* @param {object} domPropertyConfig the config as described above.
*/
injectDOMPropertyConfig: function(domPropertyConfig) {
var Properties = domPropertyConfig.Properties || {};
var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {};
var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
if (domPropertyConfig.isCustomAttribute) {
DOMProperty._isCustomAttributeFunctions.push(
domPropertyConfig.isCustomAttribute
);
}
for (var propName in Properties) {
invariant(
!DOMProperty.isStandardName[propName],
'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' +
'\'%s\' which has already been injected. You may be accidentally ' +
'injecting the same DOM property config twice, or you may be ' +
'injecting two configs that have conflicting property names.',
propName
);
DOMProperty.isStandardName[propName] = true;
DOMProperty.getAttributeName[propName] =
DOMAttributeNames[propName] || propName.toLowerCase();
DOMProperty.getPropertyName[propName] =
DOMPropertyNames[propName] || propName;
var mutationMethod = DOMMutationMethods[propName];
if (mutationMethod) {
DOMProperty.getMutationMethod[propName] = mutationMethod;
}
var propConfig = Properties[propName];
DOMProperty.mustUseAttribute[propName] =
propConfig & DOMPropertyInjection.MUST_USE_ATTRIBUTE;
DOMProperty.mustUseProperty[propName] =
propConfig & DOMPropertyInjection.MUST_USE_PROPERTY;
DOMProperty.hasBooleanValue[propName] =
propConfig & DOMPropertyInjection.HAS_BOOLEAN_VALUE;
DOMProperty.hasSideEffects[propName] =
propConfig & DOMPropertyInjection.HAS_SIDE_EFFECTS;
invariant(
!DOMProperty.mustUseAttribute[propName] ||
!DOMProperty.mustUseProperty[propName],
'DOMProperty: Cannot use require using both attribute and property: %s',
propName
);
invariant(
DOMProperty.mustUseProperty[propName] ||
!DOMProperty.hasSideEffects[propName],
'DOMProperty: Properties that have side effects must use property: %s',
propName
);
}
}
};
var defaultValueCache = {};
/**
* DOMProperty exports lookup objects that can be used like functions:
*
* > DOMProperty.isValid['id']
* true
* > DOMProperty.isValid['foobar']
* undefined
*
* Although this may be confusing, it performs better in general.
*
* @see http://jsperf.com/key-exists
* @see http://jsperf.com/key-missing
*/
var DOMProperty = {
/**
* Checks whether a property name is a standard property.
* @type {Object}
*/
isStandardName: {},
/**
* Mapping from normalized names to attribute names that differ. Attribute
* names are used when rendering markup or with `*Attribute()`.
* @type {Object}
*/
getAttributeName: {},
/**
* Mapping from normalized names to properties on DOM node instances.
* (This includes properties that mutate due to external factors.)
* @type {Object}
*/
getPropertyName: {},
/**
* Mapping from normalized names to mutation methods. This will only exist if
* mutation cannot be set simply by the property or `setAttribute()`.
* @type {Object}
*/
getMutationMethod: {},
/**
* Whether the property must be accessed and mutated as an object property.
* @type {Object}
*/
mustUseAttribute: {},
/**
* Whether the property must be accessed and mutated using `*Attribute()`.
* (This includes anything that fails `<propName> in <element>`.)
* @type {Object}
*/
mustUseProperty: {},
/**
* Whether the property should be removed when set to a falsey value.
* @type {Object}
*/
hasBooleanValue: {},
/**
* Whether or not setting a value causes side effects such as triggering
* resources to be loaded or text selection changes. We must ensure that
* the value is only set if it has changed.
* @type {Object}
*/
hasSideEffects: {},
/**
* All of the isCustomAttribute() functions that have been injected.
*/
_isCustomAttributeFunctions: [],
/**
* Checks whether a property name is a custom attribute.
* @method
*/
isCustomAttribute: function(attributeName) {
return DOMProperty._isCustomAttributeFunctions.some(
function(isCustomAttributeFn) {
return isCustomAttributeFn.call(null, attributeName);
}
);
},
/**
* Returns the default property value for a DOM property (i.e., not an
* attribute). Most default values are '' or false, but not all. Worse yet,
* some (in particular, `type`) vary depending on the type of element.
*
* TODO: Is it better to grab all the possible properties when creating an
* element to avoid having to create the same element twice?
*/
getDefaultValueForProperty: function(nodeName, prop) {
var nodeDefaults = defaultValueCache[nodeName];
var testElement;
if (!nodeDefaults) {
defaultValueCache[nodeName] = nodeDefaults = {};
}
if (!(prop in nodeDefaults)) {
testElement = document.createElement(nodeName);
nodeDefaults[prop] = testElement[prop];
}
return nodeDefaults[prop];
},
injection: DOMPropertyInjection
};
module.exports = DOMProperty;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule DOMPropertyOperations
* @typechecks
* @typechecks static-only
*/
"use strict";
@@ -86,6 +86,31 @@ var DOMPropertyOperations = {
} else if (DOMProperty.isCustomAttribute(name)) {
node.setAttribute(name, value);
}
},
/**
* Deletes the value for a property on a node.
*
* @param {DOMElement} node
* @param {string} name
*/
deleteValueForProperty: function(node, name) {
if (DOMProperty.isStandardName[name]) {
var mutationMethod = DOMProperty.getMutationMethod[name];
if (mutationMethod) {
mutationMethod(node, undefined);
} else if (DOMProperty.mustUseAttribute[name]) {
node.removeAttribute(DOMProperty.getAttributeName[name]);
} else {
var propName = DOMProperty.getPropertyName[name];
node[propName] = DOMProperty.getDefaultValueForProperty(
node.nodeName,
name
);
}
} else if (DOMProperty.isCustomAttribute(name)) {
node.removeAttribute(name);
}
}
};

View File

@@ -16,6 +16,8 @@
* @providesModule Danger
*/
/*jslint evil: true, sub: true */
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
@@ -48,12 +50,86 @@ if (__DEV__) {
};
}
var dummies = {};
/**
* Dummy container used to render all markup.
*/
var dummyNode = ExecutionEnvironment.canUseDOM ?
document.createElement('div') :
null;
function getParentDummy(parent) {
var parentTag = parent.tagName;
return dummies[parentTag] ||
(dummies[parentTag] = document.createElement(parentTag));
/**
* Some browsers cannot use `innerHTML` to render certain elements standalone,
* so we wrap them, render the wrapped nodes, then extract the desired node.
*/
var markupWrap = {
'option': [1, '<select multiple="true">', '</select>'],
'legend': [1, '<fieldset>', '</fieldset>'],
'area': [1, '<map>', '</map>'],
'param': [1, '<object>', '</object>'],
'thead': [1, '<table>', '</table>'],
'tr': [2, '<table><tbody>', '</tbody></table>'],
'col': [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>']
};
markupWrap['optgroup'] = markupWrap['option'];
markupWrap['tbody'] = markupWrap['thead'];
markupWrap['tfoot'] = markupWrap['thead'];
markupWrap['colgroup'] = markupWrap['thead'];
markupWrap['caption'] = markupWrap['thead'];
markupWrap['th'] = markupWrap['td'];
/**
* In IE8, certain elements cannot render alone, so wrap all elements.
*/
var defaultWrap = [1, '?<div>', '</div>'];
/**
* Feature detection, remove wraps that are unnecessary for the current browser.
*/
if (dummyNode) {
for (var nodeName in markupWrap) {
if (!markupWrap.hasOwnProperty(nodeName)) {
continue;
}
dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';
if (dummyNode.firstChild) {
markupWrap[nodeName] = null;
}
}
dummyNode.innerHTML = '<link />';
if (dummyNode.firstChild) {
defaultWrap = null;
}
}
/**
* Renders markup into nodes. The returned HTMLCollection is live and should be
* used immediately (or at least before the next invocation to `renderMarkup`).
*
* NOTE: Extracting the `nodeName` does not require a regular expression match
* because we make assumptions about React-generated markup (i.e. there are no
* spaces surrounding the opening tag and there is at least one attribute).
* @see http://jsperf.com/extract-nodename
*
* @param {string} markup
* @return {*} An HTMLCollection.
*/
function renderMarkup(markup) {
var node = dummyNode;
var nodeName = markup.substring(1, markup.indexOf(' '));
var wrap = markupWrap[nodeName.toLowerCase()] || defaultWrap;
if (wrap) {
node.innerHTML = wrap[1] + markup + wrap[2];
var wrapDepth = wrap[0];
while (wrapDepth--) {
node = node.lastChild;
}
} else {
node.innerHTML = markup;
}
return node.childNodes;
}
/**
@@ -63,7 +139,7 @@ function getParentDummy(parent) {
* @param {Element} elem Parent element.
* @param {Element} insert Element to insert.
* @param {Element} after Element to insert after.
* @returns {Element} Element that was inserted.
* @return {Element} Element that was inserted.
*/
function insertNodeAfterNode(elem, insert, after) {
if (__DEV__) {
@@ -120,9 +196,7 @@ function dangerouslyInsertMarkupAt(parentNode, markup, index) {
if (__DEV__) {
validateMarkupParams(parentNode, markup);
}
var parentDummy = getParentDummy(parentNode);
parentDummy.innerHTML = markup;
var htmlCollection = parentDummy.childNodes;
var htmlCollection = renderMarkup(markup);
var afterNode = index ? parentNode.childNodes[index - 1] : null;
inefficientlyInsertHTMLCollectionAfter(parentNode, htmlCollection, afterNode);
}
@@ -141,9 +215,7 @@ function dangerouslyReplaceNodeWithMarkup(childNode, markup) {
if (__DEV__) {
validateMarkupParams(parentNode, markup);
}
var parentDummy = getParentDummy(parentNode);
parentDummy.innerHTML = markup;
var htmlCollection = parentDummy.childNodes;
var htmlCollection = renderMarkup(markup);
if (__DEV__) {
throwIf(htmlCollection.length !== 1, NO_MULTI_MARKUP);
}

Some files were not shown because too many files have changed in this diff Show More