-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
State: Refactor post dirty state as higher-order reducer #3298
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3298 +/- ##
==========================================
- Coverage 31.26% 31.06% -0.21%
==========================================
Files 244 245 +1
Lines 6736 6716 -20
Branches 1204 1205 +1
==========================================
- Hits 2106 2086 -20
Misses 3895 3895
Partials 735 735
Continue to review full report at Codecov.
|
editor/state/save-state.js
Outdated
} | ||
|
||
return result; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering why this needs to be a middleware. Can't we do this simply with a "reducer enhancer":
enhancedEditorReducer( state, action ) {
const newState = editorReducer( state, action );
const isDirty = some( newState, ( value, key ) => key !== 'dirty' && value !== state[ key ] );
if ( isDirty !== newState.dirty ) {
newState.dirty = isDirty;
}
return newState;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea, and it sounds like a nice simplification. It would mean that the dirty flag would become part of editor state subtree, but I think that's reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think I missed something here, When do we reset the isDirty to false. I think this enhancer needs to be in the higher level to take into account resetting the dirty
value when the currentPost changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, unless we could rely on specific action types as signals for reset (RESET_POST
, UPDATE_POST
), then the reducer enhancer for editor could simply listen for those.
Also in your original example, ideally we don't need the some
and could do a strict equality check newState !== state
.
a76f5f4
to
93ff853
Compare
I've updated the branch with suggested approach of implementing dirty detection as a higher-order reducer. In fact, this works quite well, but unfortunately it surfaced an unrelated issue with some magic that we had introduced in the undoable reducer enhancer with property getters. The specific behavior was intended as a convenient to allow Investigating e2e failure which seems to be a legitimate issue... |
Okay, I tracked the issue down to missing a couple dependant's key updates which were more difficult to find due to use of Lodash's Fixed in e9d8a69 . Consider that a success for e2e tests 😄 To be fair, this probably would have been surfaced if the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit-pick: an undoableReducer
would be the enhanced reducer, not the higher-order function producing it. Is it a reducerer? ;) I wonder if a enhance*
, with*
or makeUndoable
-type nomenclature would be useful.
Everything else obviously looks good.
|
||
/** | ||
* Reducer enhancer which transforms the result of the original reducer into an | ||
* object tracking its own history (past, present, future). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Description for undoable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copypasta 😄 Fixed in 4459741
* | ||
* @param {Function} reducer Original reducer | ||
* @param {?Object} options Optional options | ||
* @param {?Array} options.resetTypes Action types upon which to clear past |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Param description for undoable
.
Yeah, I contemplated this, particularly in renaming what was previously |
Adds complexity (preserving getters/setters through cloning), obscurity (magic proxying of keys to history subkey), and is not respecting of desired plain object structure of state.
Better reflecting the fact that undoableReducer is not itself a reducer, but a function generating an enhanced reducer.
4459741
to
0304b60
Compare
Did another rebase, squashing a few commits. In 0304b60, I renamed the higher-order reducers to a consistent This is resolving a pretty noticeable bug, and there were a number of changes here particularly touching the treatment of history-enhanced reducers, so I'm inclined to merge this sooner than later to shake out any issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this, tested and seems to work better now.
Fixes #3296
Related: #1996
This pull request seeks to refactor the state behavior for determining whether a post has unsaved changes. In some ways it reverts to characteristics prior to #1996 in that the proposed changes here no longer perform a diff between the current and saved variations of a post. This is proposed for two reasons:
isEditedPostDirty
selector we'd otherwise need a reference to blocks at the time of the last save (either tracking history at specific action intervals, or a separate reducer)isEditedPostDirty
selector, while memoized, contains non-trivial logic (multiple deep object comparison), and is cachebust on a regular basis (each edit), thus potentially harming performance.As implemented here, the basic idea is that we check references on
state.editor
before and after any action is dispatched to the store, _using awithChangeDetection
Redux higher-order reducer. Since this reducer value encompasses any edits which occur, a strict reference equality check should be sufficient for determining if an edit has been made. This is made more challenging by:The latter of these, while "nice to have", is not as critical a feature as I might have originally assumed.
Implementation notes:
The state structuring here partially reflects the proposed Ducks pattern of #3012, originally planned to be iterated toward as part of #3030, but prioritized into these changes as a critical bug fix.Testing instructions:
Repeat testing instructions from #3296, verifying Expected Result.
Verify that Save Draft button only appears at expected intervals: