diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index f1f7163bc63..593745684af 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -124,13 +124,13 @@ function warnAboutUnstableUse() { var ReactDOM = { - render(element : ReactElement, container : DOMContainerElement) { + render(element : ReactElement, container : DOMContainerElement, callback: ?Function) { warnAboutUnstableUse(); let root; if (!container._reactRootContainer) { - root = container._reactRootContainer = DOMRenderer.mountContainer(element, container); + root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, callback); } else { - DOMRenderer.updateContainer(element, root = container._reactRootContainer); + DOMRenderer.updateContainer(element, root = container._reactRootContainer, callback); } return DOMRenderer.getPublicRootInstance(root); }, diff --git a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js index 96f447505b2..ce6109750d6 100644 --- a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js +++ b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js @@ -43,6 +43,26 @@ describe('ReactDOMFiber', () => { expect(container.textContent).toEqual('10'); }); + it('should be called a callback argument', () => { + // mounting phase + let called = false; + ReactDOM.render( +
Foo
, + container, + () => called = true + ); + expect(called).toEqual(true); + + // updating phase + called = false; + ReactDOM.render( +
Foo
, + container, + () => called = true + ); + expect(called).toEqual(true); + }); + if (ReactDOMFeatureFlags.useFiber) { it('should render a component returning strings directly from render', () => { const Text = ({value}) => value; diff --git a/src/renderers/noop/ReactNoop.js b/src/renderers/noop/ReactNoop.js index 0ac23ac0c3f..ce999148fd5 100644 --- a/src/renderers/noop/ReactNoop.js +++ b/src/renderers/noop/ReactNoop.js @@ -146,11 +146,11 @@ var ReactNoop = { root: rootContainer, - render(element : ReactElement) { + render(element : ReactElement, callback: ?Function) { if (!root) { - root = NoopRenderer.mountContainer(element, rootContainer); + root = NoopRenderer.mountContainer(element, rootContainer, callback); } else { - NoopRenderer.updateContainer(element, root); + NoopRenderer.updateContainer(element, root, callback); } }, diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index a972dc56230..a91218d5aa7 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -308,6 +308,14 @@ module.exports = function(config : HostConfig) { attachRef(current, finishedWork, instance); return; } + case HostContainer: { + const instance = finishedWork.stateNode; + if (instance.callbackList) { + const { callbackList } = instance; + instance.callbackList = null; + callCallbacks(callbackList, instance.current.child.stateNode); + } + } case HostComponent: { const instance : I = finishedWork.stateNode; attachRef(current, finishedWork, instance); diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index be3b57fbb1c..243d6bdb179 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -20,6 +20,8 @@ import type { PriorityLevel } from 'ReactPriorityLevel'; var { createFiberRoot } = require('ReactFiberRoot'); var ReactFiberScheduler = require('ReactFiberScheduler'); +var { createUpdateQueue, addCallbackToQueue } = require('ReactFiberUpdateQueue'); + if (__DEV__) { var ReactFiberInstrumentation = require('ReactFiberInstrumentation'); } @@ -74,9 +76,14 @@ module.exports = function(config : HostConfig) : return { - mountContainer(element : ReactElement, containerInfo : C) : OpaqueNode { + mountContainer(element : ReactElement, containerInfo : C, callback: ?Function) : OpaqueNode { const root = createFiberRoot(containerInfo); const container = root.current; + if (callback) { + const queue = createUpdateQueue(null); + addCallbackToQueue(queue, callback); + root.callbackList = queue; + } // TODO: Use pending work/state instead of props. // TODO: This should not override the pendingWorkPriority if there is // higher priority work in the subtree. @@ -94,9 +101,16 @@ module.exports = function(config : HostConfig) : return container; }, - updateContainer(element : ReactElement, container : OpaqueNode) : void { + updateContainer(element : ReactElement, container : OpaqueNode, callback: ?Function) : void { // TODO: If this is a nested container, this won't be the root. const root : FiberRoot = (container.stateNode : any); + if (callback) { + const queue = root.callbackList ? + root.callbackList : + createUpdateQueue(null); + addCallbackToQueue(queue, callback); + root.callbackList = queue; + } // TODO: Use pending work/state instead of props. root.current.pendingProps = element; diff --git a/src/renderers/shared/fiber/ReactFiberRoot.js b/src/renderers/shared/fiber/ReactFiberRoot.js index 1c972700b05..93200d5d270 100644 --- a/src/renderers/shared/fiber/ReactFiberRoot.js +++ b/src/renderers/shared/fiber/ReactFiberRoot.js @@ -13,6 +13,7 @@ 'use strict'; import type { Fiber } from 'ReactFiber'; +import type { UpdateQueue } from 'ReactFiberUpdateQueue'; const { createHostContainerFiber } = require('ReactFiber'); @@ -25,6 +26,8 @@ export type FiberRoot = { isScheduled: boolean, // The work schedule is a linked list. nextScheduledRoot: ?FiberRoot, + // Linked list of callbacks to call after updates are committed. + callbackList: ?UpdateQueue, }; exports.createFiberRoot = function(containerInfo : any) : FiberRoot { @@ -36,6 +39,7 @@ exports.createFiberRoot = function(containerInfo : any) : FiberRoot { containerInfo: containerInfo, isScheduled: false, nextScheduledRoot: null, + callbackList: null, }; uninitializedFiber.stateNode = root; return root; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index e7acda55c52..7fd73016cd6 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -164,6 +164,7 @@ module.exports = function(config : HostConfig) { if (finishedWork.effectTag !== NoEffect) { const current = finishedWork.alternate; commitWork(current, finishedWork); + commitLifeCycles(current, finishedWork); } } diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index 21d3e2d4c58..81e48d815d8 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -37,6 +37,7 @@ describe('ReactIncremental', () => { it('should render a simple component, in steps if needed', () => { + var renderCallbackCalled = false; var barCalled = false; function Bar() { barCalled = true; @@ -52,17 +53,20 @@ describe('ReactIncremental', () => { ]; } - ReactNoop.render(); + ReactNoop.render(, () => renderCallbackCalled = true); expect(fooCalled).toBe(false); expect(barCalled).toBe(false); + expect(renderCallbackCalled).toBe(false); // Do one step of work. ReactNoop.flushDeferredPri(7 + 5); expect(fooCalled).toBe(true); expect(barCalled).toBe(false); + expect(renderCallbackCalled).toBe(false); // Do the rest of the work. ReactNoop.flushDeferredPri(50); expect(fooCalled).toBe(true); expect(barCalled).toBe(true); + expect(renderCallbackCalled).toBe(true); }); it('updates a previous render', () => { @@ -98,21 +102,22 @@ describe('ReactIncremental', () => { ); } - ReactNoop.render(); + ReactNoop.render(, () => ops.push('renderCallbackCalled')); ReactNoop.flush(); - expect(ops).toEqual(['Foo', 'Header', 'Content', 'Footer']); + expect(ops).toEqual(['Foo', 'Header', 'Content', 'Footer', 'renderCallbackCalled']); ops = []; - ReactNoop.render(); + ReactNoop.render(, () => ops.push('firstRenderCallbackCalled')); + ReactNoop.render(, () => ops.push('secondRenderCallbackCalled')); ReactNoop.flush(); // TODO: Test bail out of host components. This is currently unobservable. // Since this is an update, it should bail out and reuse the work from // Header and Content. - expect(ops).toEqual(['Foo', 'Content']); + expect(ops).toEqual(['Foo', 'Content', 'firstRenderCallbackCalled', 'secondRenderCallbackCalled']); });