Skip to content
This repository was archived by the owner on Feb 6, 2023. It is now read-only.

Commit 90a8f22

Browse files
flarniefacebook-github-bot
authored andcommitted
Update 'latestEditorState' in render too
Summary: **tldr;** Handlers can get called after cWU and before cDU, and they use the cached 'latestEditorState'. For them to get the fresh version of the state, we need to update 'latestEditorState' in 'render' because that happens before the handlers get called. **The whole story;** We are trying to remove cWU from `DraftEditor.react.js`. One thing which currently happens there is that `latestEditorState` gets set to the nextProps.editorState, so whatever is passed in by the parent. For more context on the initial attempt at changing this, see https://our.intern.facebook.com/intern/diff/D6873303/ This change introduced one bug that manifested in various ways - see T26034821 and T26020049. I'll focus on T26020049, which broke `<AdsBulkTokenizedTextInputWithFieldsSelector>`. `<AdsBulkTok...FieldsSelection>` has a button for adding a new token, and when you hit that button it passes `<DraftEditor>` an updated editorState as a prop. This triggers render #1, and during that render we update the browser selection, which triggers `focus` and `blur` events. These each trigger another render of `<DraftEditor>`; renders #2 and #3. Here is how it looks on a timeline: ``` render #1 + | | cWU -> latestEditorState = FRESH_STATE | | render -> this.props.editorState = FRESH_STATE | + | | | +--> triggers 'focus' event, calling 'handleFocus' with latestEditorState | + | | +>cDU fires | the 'handleFocus' call schedules render #2 | with latestEditorState, which is FRESH_STATE | render #2 <--------+ + | | cWU -> latestEditorState = FRESH_STATE | | render -> this.props.editorState = FRESH_STATE | +>cDU fires ``` When we move the update of latestEditorState to cDM, things go awry like this; ``` render #1 + | | cWU -> Nothing ... latestEditorState = STALE_STATE :( | | render -> this.props.editorState = FRESH_STATE | + | | | +--> triggers 'focus' event, calling 'handleFocus' with latestEditorState | + | | +>cDU -> latestEditorState = FRESH_STATE | the 'handleFocus' call schedules render #2 | with latestEditorState, which is STALE_STATE :( | render #2 <--------------------------------------+ + | | cWU -> nothing updates | | render -> this.props.editorState = STALE_STATE :( because this was passed in above | +>cDU fires and resets latestEditorState = STALE_STATE :( ``` So we can fix things by updating latestEditorState inside the `render` method, like so; ``` render #1 + | | cWU -> Nothing ... latestEditorState = STALE_STATE :( | | render -> this.props.editorState = FRESH_STATE | + *and* set latestEditorState = FRESH_STATE | | | | +--> triggers 'focus' event, calling 'handleFocus' with latestEditorState | + | | +>cDU -> latestEditorState = FRESH_STATE | the 'handleFocus' call schedules render #2 | with latestEditorState, which is FRESH_STATE | render #2 <--------------------------------------+ + | | cWU -> nothing updates | | render -> this.props.editorState = FRESH_STATE which was passed in above | +>cDU fires and resets latestEditorState = FRESH_STATE ``` One possible issue would be if `render` fired and then was never completed, in async. mode, but since Draft is intended to always be run in sync. mode I'm not worried about that. Reviewed By: mitermayer Differential Revision: D6994261 fbshipit-source-id: 80986b853a57f64aa5aafbe667b4d94171d5271c
1 parent a6317e6 commit 90a8f22

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

src/component/base/DraftEditor.react.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,43 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
237237
}
238238

239239
render(): React.Node {
240+
if (gkx('draft_js_remove_componentwillupdate')) {
241+
/**
242+
* Sometimes a render triggers a 'focus' or other event, and that will
243+
* schedule a second render pass.
244+
* In order to make sure the second render pass gets the latest editor
245+
* state, we update it here.
246+
* Example:
247+
* render #1
248+
* +
249+
* |
250+
* | cWU -> Nothing ... latestEditorState = STALE_STATE :(
251+
* |
252+
* | render -> this.props.editorState = FRESH_STATE
253+
* | + *and* set latestEditorState = FRESH_STATE
254+
* |
255+
* | |
256+
* | +--> triggers 'focus' event, calling 'handleFocus' with latestEditorState
257+
* | +
258+
* | |
259+
* +>cdU -> latestEditorState = FRESH_STATE | the 'handleFocus' call schedules render #2
260+
* | with latestEditorState, which is FRESH_STATE
261+
* |
262+
* render #2 <--------------------------------------+
263+
* +
264+
* |
265+
* | cwU -> nothing updates
266+
* |
267+
* | render -> this.props.editorState = FRESH_STATE which was passed in above
268+
* |
269+
* +>cdU fires and resets latestEditorState = FRESH_STATE
270+
* ---
271+
* Note that if we don't set latestEditorState in 'render' in the above
272+
* diagram, then STALE_STATE gets passed to render #2.
273+
*/
274+
275+
this._latestEditorState = this.props.editorState;
276+
}
240277
const {
241278
blockRenderMap,
242279
blockRendererFn,

src/component/contents/DraftEditorLeaf.react.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ class DraftEditorLeaf extends React.Component<Props> {
117117
shouldComponentUpdate(nextProps: Props): boolean {
118118
const leafNode = ReactDOM.findDOMNode(this.leaf);
119119
invariant(leafNode, 'Missing leafNode');
120-
return (
120+
const shouldUpdate =
121121
leafNode.textContent !== nextProps.text ||
122122
nextProps.styleSet !== this.props.styleSet ||
123-
nextProps.forceSelection
124-
);
123+
nextProps.forceSelection;
124+
return shouldUpdate;
125125
}
126126

127127
componentDidUpdate(): void {

0 commit comments

Comments
 (0)