diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index 9a5473bfd9..375a443f3e 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 f65bbb000e..f7f44bff02 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 9b9c13673f..3e299a3aa3 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 c12c95629d..2503e0d02b 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -322,6 +322,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 f775e6b1f0..d5995a3c65 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'); } @@ -79,9 +81,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. @@ -99,9 +106,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 1c972700b0..93200d5d27 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 bc4b8a4bf8..6ea78627da 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -173,6 +173,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 21d3e2d4c5..81e48d815d 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']); });