Skip to content

Inserter - fix block manager state handling#95

Closed
mzorz wants to merge 9 commits intofeature/inserterfrom
try/inserter-take1-fix-block-mgr-state-props
Closed

Inserter - fix block manager state handling#95
mzorz wants to merge 9 commits intofeature/inserterfrom
try/inserter-take1-fix-block-mgr-state-props

Conversation

@mzorz
Copy link
Copy Markdown
Contributor

@mzorz mzorz commented Aug 8, 2018

This PR brings the ability to focus/move/delete/create blocks back to #88 by making sure the datasource used for the RecyclerView gets re-built and set through the Block Manager component state lifecycle rather than operating on the List's datasource in a direct way, which I believe was an early optimization done to explore the ability for the RecyclerView list RN wrapper to support animations (i.e. make blocks change positions animatedly while on another level reflecting the changes to the model). IIRC these explorations were done before the Redux connection was implemented.

Before

insertertake1fixbefore
(note how tapping on one of the newly created blocks at the bottom of the list does not make the toolbar appear - that's an indication the focused prop is false)

After

insertertake1fix
(note how tapping on any block makes it be focused, making the toolbar appear)

The flow / lifecycle in this PR - instead of setting the items in the datasource directly, it only modifies the component's props on each action (move up/down, delete and create), which then makes it to the redux store and finally then receives the new information through ~componentWillReceiveProps ~in commit a12f5ac first, but then moved to the new static getDerivedStateFromProps() in 5a5a2cc and finally sets the new state there, effectively creating a new dataset for our forked RN RecyclerView.

In order to conveniently choose whether deriving and setting a new state is appropriate or not, there are 2 new specific methods isAdditionOrDeletion which checks whether there's a new block created or a block has been deleted, and isFocusContentPositionChange which checks whether a change happened in focus, content, or the position of any block.

Description / Background / Path to solution

Since the merge of #81 in commit f47cc5d, I observed an issue only when adding new blocks in PR #88 and when these blocks are implemented by the <Rich> component, which in native is in turn implemented by the AztecRN wrapper.

1 - blocks generated in the initial state can handle get/lose focused state just fine
2 - how new blocks were generated made no difference - tried to generate blocks with parse(), createBlock and manually hardcoding the values, all work well with initial state but don't work well when inserted to the datasource on the fly
3 - this started to fail only when I merged back the new Aztec based Rich component implementation (PR #81)
4 - the insertion and state keeping work alright when we use a plain object instead of the Rich component, i.e. by setting this line here to if (false) https://github.com/wordpress-mobile/gutenberg-mobile/blob/try/inserter-take1/src/block-management/block-holder.js#L49

As per my tests, the problem only appeared under these conditions:

  • the list works in a way that it makes it easier to set and keep its own datasource on each action, effectively keeping a copy of the status in its datasource.
  • the problem with focus only appears with our Android list, and only with the Aztec wrapper. It doesn't appear on iOS or when we have a plain View instead of Aztec wrapper.
  • what happens is that when creating a new block and appending to the list, then tapping on it, it will generate the right focused prop change that will hit redux, but
    when the list is re-rendered the change is not made visible, and by observation it turns out that's because the prop there will still be focused=false. Why is it false? I couldn't understand why.

Looking into all corners of the code I suspected the problem is manipulation of state in two places: locally on the List's state datasource to produce the UI changes, and on the props and signaling the redux store, not really then making good use of the lifecycle.
For example, this was being done on DELETE action:

              case ToolbarButton.DELETE:
				this.state.dataSource.splice( dataSourceBlockIndex, 1 );
				this.props.deleteBlockAction( uid );

.

Making sure the datasource was not directly accessed / modified was a first step (eliminating line 1 within the case handler above), and then making sure the state was then set after props changed made sense as part of the component's lifecycle.

Possible next steps

In the near future we might want to check how to bring some optimization back into the List (if more than say 100 or 200 blocks ever exist into a single Post), and animations can be looked into later as well. I think the way we were handling things was not being done properly. The obvious thing is, we are indeed producing a new datasource instead of only changing the items that need be changed but that's how React should work - down the line the proper Components(items in the list) should decide wether to update themselves or not within the List, but the whole state needs to be a new object each time as per how React works.

Other resources to have in mind as we discover and adapt our block manager to further uses:

https://facebook.github.io/react-native/docs/direct-manipulation
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-controlled-component
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#new-lifecycle-getderivedstatefromprops
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props

Further notes

Just adding many reviewers so to get a few of your eyes on this - it seems important and would like to get the team aware and get as many comments as possible :) 🙇

@mzorz mzorz mentioned this pull request Aug 8, 2018
4 tasks
static isAdditionOrDeletion( newProps: PropsType, currState: StateType ) {
// there's been an addition / deletion
if ( currState.dataSource.size() !== newProps.blocks.length ) {
return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This method is returning true in one case, but is missing the return value for the else execution path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good catch! addressed in e44dc5c

@koke
Copy link
Copy Markdown
Member

koke commented Aug 13, 2018

Related to this, I think #93 was only necessary because of a similar issue as the onChange method is replacing the whole list of attributes with the arguments of the call, while the redux store is only modifying the changed attributes.

Do you think it's worth including that change in this one?

@mzorz mzorz changed the base branch from try/inserter-take1 to feature/inserter August 13, 2018 12:22
@mzorz
Copy link
Copy Markdown
Contributor Author

mzorz commented Aug 13, 2018

Do you think it's worth including that change in this one?

If you mean to bring the change in #93 to this branch here, that change is already in this branch as it's been updated - also I just reviewed again your change on #93 and IIUC I don't think it's adding anything in particular that we should revert - the PR does what its advertising, i.e. it acts as something for the developer to not pay special attention to which properties should be set, which comes in handy. In this sense I believe your change should be harmless, unless I'm missing something? i.e could we have some properties overriden when we don't want to? wdyt?

Also, bringing part of a convo we had on Slack that may be worth to clarify here as well, in terms of what gets saved to the redux store and how the datasource is handled: the data source itself is not part of the state . What’s part of the state is the block list, which, if changed, needs to propagate the changes into the datasource, which we could do either comparing blocks and replicating movements/focus changes, etc or just re-creating the datasource with new DataSource(blocks)… which is what we’re doing in this PR after a decision to update the state (or not) is made in static getDerivedStateFromProps.

@hypest
Copy link
Copy Markdown
Contributor

hypest commented Aug 14, 2018

I can't say i'm fond of recreating the DataSource solution. This effectively disables the animations, nullifying one of the main drives behind using the RecyclerViewList in the first place.

Allow me to investigate a bit the root cause behind the "focus" issue and report back.

@koke
Copy link
Copy Markdown
Member

koke commented Aug 14, 2018

it acts as something for the developer to not pay special attention to which properties should be set, which comes in handy

Yes, we still want that functionality, but the redux store was already doing that for us. The problem was that we were also setting the attributes in the data source directly, overriding what was in the store.

This is what I'd propose:
https://gist.github.com/koke/627d672a6d264890582dc24a77bad5a5

@mzorz
Copy link
Copy Markdown
Contributor Author

mzorz commented Aug 14, 2018

I can't say i'm fond of recreating the DataSource solution. This effectively disables the animations, nullifying one of the main drives behind using the RecyclerViewList in the first place.

Me neither 😆

Allow me to investigate a bit the root cause behind the "focus" issue and report back.

Please do! this is exactly the reason for this PR to exist 👍 Thanks in advance 🙇

@mzorz
Copy link
Copy Markdown
Contributor Author

mzorz commented Aug 14, 2018

This is what I'd propose:
https://gist.github.com/koke/627d672a6d264890582dc24a77bad5a5

Oh I see now 😄 ! Sorry I didn't realize - will do and get back here

@mzorz
Copy link
Copy Markdown
Contributor Author

mzorz commented Aug 14, 2018

Closing this in favor of #108 - this comment #95 (comment) doesn't apply anymore because of the reasons explained in #108

hypest pushed a commit that referenced this pull request Jan 3, 2019
…-first-word-selected-ios

Blocks no longer merge when pressing backspace with a non-zero-length selection.
@SergioEstevao SergioEstevao deleted the try/inserter-take1-fix-block-mgr-state-props branch February 7, 2019 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants