-
Notifications
You must be signed in to change notification settings - Fork 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
Editor: Fix over-riding of local title edits on auto-save. #11729
Conversation
client/post-editor/post-editor.jsx
Outdated
@@ -713,9 +713,6 @@ export const PostEditor = React.createClass( { | |||
this.props.resetEditorLastDraft(); | |||
} | |||
|
|||
// Remove this when the editor is completely reduxified ( When using Redux actions for all post saving requests ) | |||
this.props.savePostSuccess( post.site_ID, this.props.postId, post, {} ); |
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.
This was introduced here #10177 and the idea IIRC was to update the editedPostId in the state when we're creating a new post. If we drop this, the editedPostId on the state will not reflect the saved post id. I'm not sure What's the impact yet.
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.
One other thing to think about. Currently, since the editor is not reduxified, saving a post is called using the old Actions but when we reduxify everything, we would probably call the savePost
action which could create the same bug. So maybe the fix for this would be to clear the edits when triggering the request instead of when receiving the response. The downside of this approach is when the save fails.
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.
Alternatively, could we update the reducer for edits to migrate them to the updated post ID when saving a new post (i.e. going from null
to valid postId
)?
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.
In my testing I haven't found any issues with removing this code. The same logic is repeated by exitPost which seems to clear up the original issue of lingering draft edits when leaving the editor.
So maybe the fix for this would be to clear the edits when triggering the request instead of when receiving the response.
This seems like a logical approach too. The action would have to clear edits specific for autoSave vs full save events since autoSave only sends Title, Excerpt and content so it should not clear out edits of other attributes.
Would you be willing to allow me to explore that approach in a subsequent PR @youknowriad and at least get this fix out to stop the loss of data? I hit this bug a few times yesterday while testing the new chrome and it really does take ones trust away from the save operations.
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.
If we remove this, we'll want to remove the prop from mapDispatchToProps
since this is the only place it's used. Just a small observation, still doing a more in-depth review...
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 finding this to be a bit problematic in its current form. Saving a new draft with just a title doesn't update the URL, doesn't change from "New Post", doesn't hide the "Save" button, and the post remains dirty (attempting to navigate away prompts). From what existed prior to baa395a, it appears we at least need to update state to reflect that the post ID has changed when saving a new draft.
Thanks for finding that issue @aduth - I will work on a solution. |
The issues were due to At some point we will need to revisit this when all is in redux, but for now this seems to be a logical fix. @aduth thoughts? |
With the latest changes, it appears to revert back to the previous issue of losing changes made while the save request is in-flight. At least it does when writing a title for a new draft. I have a feeling that it's preserving the edits under the empty (new post) |
Just a random drive-by from someone from another era, but I fixed this issue back in the day by keeping a queue of changes that occurred while a request was in-flight and then reapplying them. I imagine that subtle aspect got lost during reduxification. See https://github.com/Automattic/wp-calypso/blob/master/client/lib/posts/post-edit-store.js#L98 |
To add a tiny bit of additional color, the reason that the queue was necesary was to properly set the dirty state after one or more edit actions has occurred (perhaps mutating different properties of the post) while the save request is in-flight. |
Hi @gcorne 😄 Hope you've been well! With the current Redux state being capable of tracking multiple posts and edit values for multiple posts, I think what had previously been reflected in the queue could be imitated by tracking edits in the empty (new) post key and, at the time the save completes, simply replace (or copy values from) the key with the new post ID. Maybe a new action which notifies that the edited post's ID has changed, in this case from |
👋 @gcorne - great to see you! |
@aduth in b7c5ec9I have added logic that when the prior save operation had a null |
client/state/posts/reducer.js
Outdated
const { postId, siteId, savedPost } = action; | ||
|
||
if ( postId ) { | ||
return state; |
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.
Should we be consistent with how we exit early from the switch, between this and the break;
above which achieve the same goal?
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.
Also, should we make this so specific to the empty postId case? Presumably if it were ever the case that the ID from the savedPost differs from the postId
we sent with the save, we'd want to re-key?
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.
Not entirely sure I follow here, if the prior props.postId
was set, and it differs from savedPost.ID
? I hope that doesn't happen, but is that what you are talking about?
client/state/posts/reducer.js
Outdated
|
||
// if postId is null, copy over any edits | ||
return Object.assign( {}, state, { | ||
[ siteId ]: { |
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.
The way we're assigning here will delete any other post IDs for the same site. While in practice this will probably never occur, and conveniently removes the ''
(empty) key for us, we might want to preserve these either with _.merge
or a nested Object.assign
:
return {
...state,
[ siteId ]: {
...state[ siteId ],
[ savedPost.ID ]: state[ siteId ][ '' ]
}
};
Of course at that point we'd need to assume responsibility for removing the empty key:
const nextState = {
...state,
[ siteId ]: {
...state[ siteId ],
[ savedPost.ID ]: state[ siteId ][ '' ]
}
};
delete nextState[ siteId ][ '' ];
return nextState;
Alternatively we could use _.mapKeys
to reassign the empty key:
return {
...state,
[ siteId ]: mapKeys( state[ siteId ], ( value, key ) => (
'' === key ? savedPost.ID : key
) )
};
client/state/posts/reducer.js
Outdated
@@ -282,6 +281,23 @@ export function edits( state = {}, action ) { | |||
[ action.siteId ]: omit( state[ action.siteId ], action.postId || '' ) | |||
} ); | |||
|
|||
case POST_SAVE_SUCCESS: | |||
if ( ! state.hasOwnProperty( action.siteId ) || ! action.savedPost ) { |
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.
A little safety doesn't hurt, but is there ever a case where action.savedPost
would be falsey?
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.
Probably not, but one seems okay to err on the side of safe here.
Went with the |
Right, so the thinking is when we call But you're right, this shouldn't ever happen, and may very well have other implications if it were to, so I'm not too concerned about it. |
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.
if ( ! state.hasOwnProperty( action.siteId ) || ! action.savedPost || action.postId ) { | ||
break; | ||
} | ||
const { siteId, savedPost } = action; |
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.
Minor, not worth another round of reviews: Could move destructuring above the condition to make the condition slightly shorter:
const { siteId, savedPost, postId } = action;
if ( ! state.hasOwnProperty( siteId ) || ! savedPost || postId ) {
This branch fixes #11567 which is causing local edits of the title field to be lost when the changes are made while an auto-save is in flight.
There is a video of the bug in #11567 - but to see the bug in action, add some content to a post ( draft ) you are editing, then focus on the title field. Watch the ground control area, and as soon as an auto save fires, start changing the title, and once the API returns, your local title changes will disappear 💥
I traced the issue back to
onSaveSuccess
in<PostEditor />
which was callingsavePostSuccess
which clears out any local edits in the state tree.onSaveSuccess
also callsreceivePost
which iterates over the local edits, and compares it against the saved post from the API, and only deletes edits that match - so it seems like the call torecievePost
should be enough here ( though /cc @aduth for your thoughts ).