Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text Editor: Fix inserting blocks #1077

Merged
merged 4 commits into from
Jun 12, 2017
Merged

Conversation

youknowriad
Copy link
Contributor

closes #1072

This bug is way harder to solve than it seems because for performance reasons, we want to avoid calling parse/serialize as much as we can.

The solution here involves:

  • Making the text editor controlled
  • Tracking blocks prop updates and serialize the new content each time the prop change
  • Make sure the getBlocks selector returns the same array instance if the state didn't change
  • Trigger a "MARK_DIRTY" action each time we change the text editor value (to enable the publish button) even if the blocks value is not dispatched to the store.

@youknowriad youknowriad added the [Type] Bug An existing feature does not function as intended label Jun 8, 2017
@youknowriad youknowriad self-assigned this Jun 8, 2017
@@ -3,6 +3,7 @@
*/
import moment from 'moment';
import { first, last, get } from 'lodash';
import { createSelector } from 'reselect';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently published a Calypso-like memoization library: https://github.com/aduth/rememo

Curious if you have thoughts on whether reselect is a better approach or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't have any preference. The advantage of reselect is that it's kind of the default with Redux, more developers already know how to use it. But at the same time, rememo is probably easier to learn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I've always found the examples they chose in their documentation to be overly daunting for the sake of showcasing a complex example, but what you've shown here is much simpler.

Another worry is that they don't really prescribe easy patterns for accepting arguments to a selector:

https://github.com/reactjs/reselect#q-how-do-i-create-a-selector-that-takes-an-argument

Here worked out easy enough because getBlocks doesn't accept any anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm! Good point about the selectors with arguments, I think we'll need this quickly. Though according to their docs, we could write this

const createSelectorWithArguments = ( extractors, resolver ) => {
	const memoizedResolver = memoize( resolver );
	const memoizedSelector = createSelector( extractors, memoizedResolver );
	return ( state, ...args ) => {
		const selector = memoizedSelector( state );
		return selector( ...args );
	};
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm looks like https://github.com/toomuchdesign/re-reselect do exactly what I've proposed.

But I think I'll just switch to rememo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually! forget my implementation it's completely broken I think :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aduth I think there's a problem somewhere in rememo. I've replaced the selector by this:

export const getBlocks = createSelector(
	( state ) => {
		return state.editor.blockOrder.map( ( uid ) => (
			state.editor.blocksByUid[ uid ]
		) );
	},
	state => state.editor.blockOrder,
	state => state.editor.blocksByUid,
);

and I'm getting a new Instance even if the state didn't change.
I'll probably keep reselect for now, and feel free to update once fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youknowriad That's incorrect usage. There should be two arguments at most, so the second should be changed to:

( state ) => [
	state.editor.blockOrder,
	state.editor.blocksByUid,
]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not working 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated with the last version of rememo it works great.

@jasmussen
Copy link
Contributor

Unrelated to the conversation above, this seems to be working well! Thanks for tackling this so fast.

}

onBlur() {
const blocks = parse( this.state.value );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in chat, can we test here whether the value has changed from what we know to have been the previous value before parsing and assigning new blocks?

@aduth
Copy link
Member

aduth commented Jun 9, 2017

Total aside but on the note of memoized selectors: I've been quite intrigued by Vue's approach for change tracking using Object.defineProperty, and I feel like there could be an easy pattern for memoization in Redux applying the same technique; i.e. analyzing the structure of state, replacing each key (and subkey) with a getter, and detecting calls to these properties within the selector to determine when the cache needs to be busted (effectively replacing the need for the second argument to createSelector). I might do some explorations on this separately.

@aduth
Copy link
Member

aduth commented Jun 9, 2017

Steps to reproduce:

  1. Navigate to Demo
  2. Switch to Text
  3. Insert block

Expected: Serialized block content appended to the end of content
Actual: Serialized block content inserted between first and second blocks, regardless of where focus changes within text

Guessing our visual editor mode insertion point is interfering.

@youknowriad
Copy link
Contributor Author

Good catch @aduth. I've addressed the issue by simplifying the insertion point behaviour and moving the logic to a testable selector in 44f6fe1

Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the refactor. This works well now 👍

@youknowriad youknowriad merged commit 0284575 into master Jun 12, 2017
@youknowriad youknowriad deleted the fix/update-text-editor branch June 12, 2017 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inserter: Inserting content doesn't update in text mode
3 participants