Updater form of setState
Add support for setState((state, props) => newState). Rename pendingState to stateQueue.
Andrew Clark committed
Jul 25, 2016 at 09:09 UTC
25d199abe01befe07bed0c7d1ae55af47857650c
7 files changed
+118
-74
src/renderers/shared/fiber/ReactFiber.js
+6
-7
@@ -15,7 +15,7 @@
15
import type { ReactCoroutine, ReactYield } from 'ReactCoroutine';
16
import type { TypeOfWork } from 'ReactTypeOfWork';
17
import type { PriorityLevel } from 'ReactPriorityLevel';
18
-import type { PendingState } from 'ReactFiberPendingState';
18
+import type { StateQueue } from 'ReactFiberStateQueue';
19
20
var ReactTypeOfWork = require('ReactTypeOfWork');
21
var {
@@ -77,9 +77,8 @@ export type Fiber = Instance & {
77
pendingProps: any, // This type will be more specific once we overload the tag.
78
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
79
memoizedProps: any, // The props used to create the output.
80
- // Local state for class components. Either null or a linked list of partial
81
- // state objects.
82
- pendingState: PendingState,
80
+ // A queue of local state updates.
81
+ stateQueue: StateQueue,
82
// The state used to create the output. This is a full state object.
83
memoizedState: any,
84
// Output is the return value of this fiber, or a linked list of return values
@@ -157,7 +156,7 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
156
157
pendingProps: null,
158
memoizedProps: null,
160
- pendingState: null,
159
+ stateQueue: null,
160
memoizedState: null,
161
output: null,
162
@@ -200,7 +199,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
199
alt.sibling = fiber.sibling; // This should always be overridden. TODO: null
200
alt.ref = fiber.ref;
201
alt.pendingProps = fiber.pendingProps; // TODO: Pass as argument.
203
- alt.pendingState = fiber.pendingState;
202
+ alt.stateQueue = fiber.stateQueue;
203
alt.pendingWorkPriority = priorityLevel;
204
205
alt.child = fiber.child;
@@ -226,7 +225,7 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
225
// pendingProps is here for symmetry but is unnecessary in practice for now.
226
// TODO: Pass in the new pendingProps as an argument maybe?
227
alt.pendingProps = fiber.pendingProps;
229
- alt.pendingState = fiber.pendingState;
228
+ alt.stateQueue = fiber.stateQueue;
229
alt.pendingWorkPriority = priorityLevel;
230
231
alt.memoizedProps = fiber.memoizedProps;
src/renderers/shared/fiber/ReactFiberBeginWork.js
+20
-25
@@ -18,7 +18,7 @@ import type { FiberRoot } from 'ReactFiberRoot';
18
import type { HostConfig } from 'ReactFiberReconciler';
19
import type { Scheduler } from 'ReactFiberScheduler';
20
import type { PriorityLevel } from 'ReactPriorityLevel';
21
-import type { PendingState } from 'ReactFiberPendingState';
21
+import type { StateQueue } from 'ReactFiberStateQueue';
22
23
var {
24
reconcileChildFibers,
@@ -42,11 +42,12 @@ var {
42
OffscreenPriority,
43
} = require('ReactPriorityLevel');
44
var {
45
- createPendingState,
46
- mergePendingState,
47
-} = require('ReactFiberPendingState');
45
+ createStateQueue,
46
+ addToQueue,
47
+ mergeStateQueue,
48
+} = require('ReactFiberStateQueue');
49
49
-module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler: () => Scheduler) {
50
+module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getScheduler : () => Scheduler) {
51
52
function markChildAsProgressed(current, workInProgress, priorityLevel) {
53
// We now have clones. Let's store them as the currently progressed work.
@@ -113,9 +114,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
114
return workInProgress.child;
115
}
116
116
- function scheduleUpdate(fiber: Fiber, pendingState: PendingState, priorityLevel : PriorityLevel): void {
117
+ function scheduleUpdate(fiber: Fiber, stateQueue: StateQueue, priorityLevel : PriorityLevel): void {
118
const { scheduleLowPriWork } = getScheduler();
118
- fiber.pendingState = pendingState;
119
+ fiber.stateQueue = stateQueue;
120
while (true) {
121
if (fiber.pendingWorkPriority === NoWork ||
122
fiber.pendingWorkPriority >= priorityLevel) {
@@ -138,26 +139,20 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
139
const updater = {
140
enqueueSetState(instance, partialState) {
141
const fiber = instance._fiber;
141
- let pendingState = fiber.pendingState;
142
+ let stateQueue = fiber.stateQueue;
143
144
// Append to pending state queue
144
- const nextPendingStateNode = createPendingState(partialState);
145
- if (pendingState === null) {
146
- pendingState = nextPendingStateNode;
145
+ if (stateQueue === null) {
146
+ stateQueue = createStateQueue(partialState);
147
} else {
148
- if (pendingState.tail === null) {
149
- pendingState.next = nextPendingStateNode;
150
- } else {
151
- pendingState.tail.next = nextPendingStateNode;
152
- }
153
- pendingState.tail = nextPendingStateNode;
148
+ addToQueue(stateQueue, partialState);
149
}
150
151
// Must schedule an update on both alternates, because we don't know tree
152
// is current.
158
- scheduleUpdate(fiber, pendingState, LowPriority);
153
+ scheduleUpdate(fiber, stateQueue, LowPriority);
154
if (fiber.alternate) {
160
- scheduleUpdate(fiber.alternate, pendingState, LowPriority);
155
+ scheduleUpdate(fiber.alternate, stateQueue, LowPriority);
156
}
157
},
158
};
@@ -171,12 +166,12 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
166
props = current.memoizedProps;
167
}
168
// Compute the state using the memoized state and the pending state queue.
174
- var pendingState = workInProgress.pendingState;
169
+ var stateQueue = workInProgress.stateQueue;
170
var state;
171
if (!current) {
177
- state = mergePendingState(null, pendingState);
172
+ state = mergeStateQueue(null, props, stateQueue);
173
} else {
179
- state = mergePendingState(current.memoizedState, pendingState);
174
+ state = mergeStateQueue(current.memoizedState, props, stateQueue);
175
}
176
177
var instance = workInProgress.stateNode;
@@ -186,7 +181,7 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
181
state = instance.state || null;
182
// The initial state must be added to the pending state queue in case
183
// setState is called before the initial render.
189
- workInProgress.pendingState = createPendingState(state);
184
+ workInProgress.stateQueue = createStateQueue(state);
185
// The instance needs access to the fiber so that it can schedule updates
186
instance._fiber = workInProgress;
187
instance.updater = updater;
@@ -334,9 +329,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>, getSchedu
329
330
if ((workInProgress.pendingProps === null || (
331
workInProgress.memoizedProps !== null &&
337
- workInProgress.pendingProps === workInProgress.memoizedProps &&
332
+ workInProgress.pendingProps === workInProgress.memoizedProps
333
)) &&
339
- workInProgress.pendingState === null
334
+ workInProgress.stateQueue === null
335
) {
336
return bailoutOnAlreadyFinishedWork(current, workInProgress);
337
}
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+2
-2
@@ -132,8 +132,8 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
132
return null;
133
case ClassComponent:
134
transferOutput(workInProgress.child, workInProgress);
135
- // Don't use the pending state queue to compute the memoized state. We
136
- // already merged it and assigned it to the instance. Copy it from there.
135
+ // Don't use the state queue to compute the memoized state. We already
136
+ // merged it and assigned it to the instance. Copy it from there.
137
const state = workInProgress.stateNode.state;
138
workInProgress.memoizedState = state;
139
return null;
src/renderers/shared/fiber/ReactFiberPendingState.js
deleted
-38
@@ -1,38 +0,0 @@
1
-/**
2
- * Copyright 2013-present, Facebook, Inc.
3
- * All rights reserved.
4
- *
5
- * This source code is licensed under the BSD-style license found in the
6
- * LICENSE file in the root directory of this source tree. An additional grant
7
- * of patent rights can be found in the PATENTS file in the same directory.
8
- *
9
- * @providesModule ReactFiberPendingState
10
- * @flow
11
- */
12
-
13
-'use strict';
14
-
15
-export type PendingState = {
16
- partialState: any,
17
- next: PendingState,
18
- tail: PendingState
19
-} | null;
20
-
21
-exports.createPendingState = function(partialState: mixed): PendingState {
22
- return {
23
- partialState,
24
- next: null,
25
- tail: null,
26
- };
27
-};
28
-
29
-exports.mergePendingState = function(prevState: any, queue: PendingState): any {
30
- if (queue === null) {
31
- return prevState;
32
- }
33
- let state = Object.assign({}, prevState, queue.partialState);
34
- while (queue = queue.next) {
35
- state = Object.assign(state, queue.partialState);
36
- }
37
- return state;
38
-};
src/renderers/shared/fiber/ReactFiberScheduler.js
+2
-2
@@ -143,9 +143,9 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
143
// The work is now done. We don't need this anymore. This flags
144
// to the system not to redo any work here.
145
workInProgress.pendingProps = null;
146
- workInProgress.pendingState = null;
146
+ workInProgress.stateQueue = null;
147
if (current) {
148
- current.pendingState = null;
148
+ current.stateQueue = null;
149
}
150
151
const returnFiber = workInProgress.return;
src/renderers/shared/fiber/ReactFiberStateQueue.js
new
+51
@@ -0,0 +1,51 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactFiberStateQueue
10
+ * @flow
11
+ */
12
+
13
+'use strict';
14
+
15
+export type StateQueue = {
16
+ partialState: any,
17
+ next: StateQueue,
18
+ tail: StateQueue
19
+} | null;
20
+
21
+exports.createStateQueue = function(partialState : mixed) : StateQueue {
22
+ return {
23
+ partialState,
24
+ next: null,
25
+ tail: null,
26
+ };
27
+};
28
+
29
+exports.addToQueue = function(queue : StateQueue, partialState : mixed) {
30
+ const node = exports.createStateQueue(partialState);
31
+ if (queue.tail === null) {
32
+ queue.next = node;
33
+ } else {
34
+ queue.tail.next = node;
35
+ }
36
+ queue.tail = node;
37
+}
38
+
39
+exports.mergeStateQueue = function(prevState : any, props : any, queue : StateQueue) : any {
40
+ if (queue === null) {
41
+ return prevState;
42
+ }
43
+ let state = Object.assign({}, prevState);
44
+ do {
45
+ const partialState = typeof queue.partialState === 'function' ?
46
+ queue.partialState(state, props) :
47
+ queue.partialState;
48
+ state = Object.assign(state, partialState);
49
+ } while (queue = queue.next);
50
+ return state;
51
+};
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+37
@@ -616,4 +616,41 @@ describe('ReactIncremental', () => {
616
ReactNoop.flush();
617
expect(instance.state).toEqual({ a: 'a', b: 'b', c: 'c', d: 'd' });
618
});
619
+
620
+ it('can use updater form of setState', () => {
621
+ let instance;
622
+ class Bar extends React.Component {
623
+ constructor() {
624
+ super();
625
+ this.state = { num: 1 };
626
+ instance = this;
627
+ }
628
+ render() {
629
+ return <div>{this.props.children}</div>;
630
+ }
631
+ }
632
+
633
+ function Foo({ multiplier }) {
634
+ return (
635
+ <div>
636
+ <Bar multiplier={multiplier} />
637
+ </div>
638
+ );
639
+ }
640
+
641
+ function updater(state, props) {
642
+ return { num: state.num * props.multiplier };
643
+ }
644
+
645
+ ReactNoop.render(<Foo multiplier={2} />);
646
+ ReactNoop.flush();
647
+ expect(instance.state.num).toEqual(1);
648
+ instance.setState(updater);
649
+ ReactNoop.flush();
650
+ expect(instance.state.num).toEqual(2);
651
+ ReactNoop.render(<Foo multiplier={3} />);
652
+ instance.setState(updater);
653
+ ReactNoop.flush();
654
+ expect(instance.state.num).toEqual(6);
655
+ });
656
});