-
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: Reset Post Edits when switching posts in the post editor #10177
Conversation
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, this resolves the specific case I'd encountered 👍 One effect of this is that resets are fired after saving a new draft (no ID to saved ID) and when switching to a draft in the draft drawer, but it doesn't seem that this would problematic.
Another case which doesn't quite behave as I'd expect:
- Start a new post
- Toggle sticky
- Click Write in master bar and select same site
Expected: Sticky should be reset?
Actual: Sticky not reset
I'm not sure whether this would be expected or not. If it were, maybe we need to fire the reset from the new post controller.
@@ -156,6 +156,12 @@ export const PostEditor = React.createClass( { | |||
|
|||
componentWillReceiveProps: function( nextProps ) { | |||
const { siteId, postId } = this.props; | |||
|
|||
// When switching posts, reset post edits for the post we're leaving | |||
if ( nextProps.postId !== 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.
A very edge case that might occur is switching from editing posts of the same ID on two different sites. Should we also check whether the site ID has changed?
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.
Yeah, I think we should. I though these IDs were unique between sites.
@aduth The use-case you mentioned is probably worth fixing since the title/content is reset if we trigger a new Post from the master bar. Fixed in f38d7ba |
@@ -115,6 +115,7 @@ module.exports = { | |||
|
|||
function startEditing( siteId ) { | |||
context.store.dispatch( setEditorPostId( 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.
Just a thought - should the reducer for state.posts.edits
be listening for the EDITOR_POST_ID_SET
action type and reset itself internally, rather than us calling the reset action explicitly? A possible complication from this is that we might not know that the ID is different, though ideally we're only dispatching this action in cases where the ID has in-fact changed. Or we could track ID in edits
itself when EDITOR_POST_ID_SET
is dispatched and compare against it in the reducer.
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.
After taking a look at where this action is used now, It looks like it's a good fit, but I'm wondering if it's not risky. One could use this action elsewhere and not knowing about the consequences. Should we rename it to something like startPostEditor( siteID, postID )
. I don't think actions are meant to be "setters".
Also, does this mean, we should drop the current resetPostEdits
calls (componentWillUnmount, componentWillReceiveProps, onSaveSuccess)? I would say yes, but maybe this would change the behavior a bit.
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 agree that a more meaningfully named action creator might help avoid accidents. Do you think it would be used in addition to or in place of the existing setEditorPostId
? If in addition to, would we treat it as a totally separate action type or could we extend the existing action type with additional details / meta to describe it as being a "new" editing session?
Also, does this mean, we should drop the current
resetPostEdits
calls (componentWillUnmount
,componentWillReceiveProps
,onSaveSuccess
)? I would say yes, but maybe this would change the behavior a bit.
I think it'd be nice to allow the editor component to not worry about these details. If I were to guess, I suspect we reset upon unmounting as a sort of clean-up task, so the state doesn't hang around after leaving the editor. As long as we properly reset when revisiting the editor, I don't know that there will be any problems. Otherwise we could fire some new stopEditing
action creator upon unmounting / leaving the editor that has similar effects of resetting edit 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.
Otherwise we could fire some new
stopEditing
action creator upon unmounting / leaving the editor that has similar effects of resetting edit state.
This is now ringing bells that we'd previously reset edits on a page.exit
handler but changed away from that approach for some reason. Let me see if I can dig that up.
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.
Do you think it would be used in addition to or in place of the existing setEditorPostId ?
I was thinking as a replacement on the controller, but I'm still wondering if I should keep the setEditorPostId
for the specific usecase when saving a new post with ID yet, we'll have to update the ID without "starting a new editing session".
Let me try to put that in place, to have more context for discussion
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.
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.
Thought, this seems more "logic"
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 just a small remark, I'm wondering why the lingering edits change the behavior of the "New" links. They seem unrelated to me. Maybe a bug on these links instead no?
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 just a small remark, I'm wondering why the lingering edits change the behavior of the "New" links. They seem unrelated to me. Maybe a bug on these links instead no?
There's an expanded explanation for the behavior on #4539
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 think what you proposed: getNewEditorPath
selector, is a good approach to solve this definitely.
Anyway, Should we merge the current approach of this branch?
dbf8b02
to
4c6278d
Compare
Brain-dumping here, since I'm concerned we have too many ways of changing the ID of the edited post and too many places where we're resetting edits. Taking a step back and trying to think through expectations: If we've changed posts or navigated away from the editor, it makes sense to drop unsaved edits for the post we had been editing. The main complication here is when a new draft is saved, since we transition from an empty post ID to the draft's new saved ID. I would like to think in that case that it would be fine to reset edits for the post, but I worry that if a user continues to make edits while a save request is in flight, those edits need to be preserved until next we save, so we can't simply reset the edits upon the save completing. With this in mind, perhaps it is best that the But then, should those edits be reset when starting or ending? I mentioned we don't want lingering state hanging around after leaving the editor, but we similarly need to reset edits when changing between posts while still in the editor (drafts drawer, master bar "Write"). As an aside, we already have some logic which needs to run when starting a post to assign the post's type, so this could fit well within an action creator which signals that we've started editing a post. With these changes, what we have is resetting edits both when starting a post and when ending, but implemented in different ways. The resetting upon starting occurs in the controller by calling Could we make this more consistent? Perhaps your solution in f38d7ba is best in simply calling Another idea is to have special-purpose Or maybe we don't care if edits linger after leaving the editor, as long as we fix the bugs observed in #4539. That way we don't need to reset anything when leaving the editor. If we do keep the new action creator, do you think it could simply extend export function startEditingPost( siteId, postId ) {
return {
...setEditorPostId( postId ),
clearEdits: true
};
} ... since almost all of the behavior is the same except the reducer logic for resetting? There we could just check for the presence of this new key. This was long, sorry 😄 Just expressing some inner turmoil being uncertain what's the clearest solution. |
Yeah difficult problem to solve and I see two way of thinking here: 1- Should we continue our Redux
2- The second solution is I think a more "redux" way of thinking but It requires bigger changes (and I'm not sure it's worth it). In this solution, we have to rethink our actions as a whole.
With this solution, all resets should be handled on the reducers, and the component should not worry about "resetting" the edits explicitly. That said, I'm more keen to try the second. |
@aduth I tried the new approach (pure redux actions) in bcfc667 and I'm pretty satisfied with the results. What do you think? |
this.props.setEditorPostId( post.ID ); | ||
} | ||
// 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.
Only by a bit of luck does this not cause a notice to be shown (which would be non-ideal, at least for now, given that it's redundant with the editor's own notice). See:
wp-calypso/client/state/notices/middleware.js
Lines 78 to 93 in 795cd4d
export function onPostSaveSuccess( dispatch, action ) { | |
let text; | |
switch ( action.post.status ) { | |
case 'trash': | |
text = translate( 'Post successfully moved to trash' ); | |
break; | |
case 'publish': | |
text = translate( 'Post successfully published' ); | |
break; | |
} | |
if ( text ) { | |
dispatch( successNotice( text ) ); | |
} | |
} |
{}
instead included a status property).
Arguably this is an argument against the lack of discoverability of middlewares 😄
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.
Yeah, I noticed these notices as well :). I'm starting to think, we should avoid using the middleware to automatically trigger notices on request success. I had to revert my own save settings global notices here #10278 to avoid double notices. I think we should rely on componentWillReceiveProps
to trigger these notices.
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 we could create separate extended action creators for a more specific variations if we only want a notice to be shown in some cases, e.g. adding a meta flag that the middleware checks the presence of.
} ); | ||
|
||
case EDITOR_STOP: | ||
case POST_SAVE_SUCCESS: |
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 wondered if clearing edits on success would have a negative impact where we currently depend on type
to be always present, but realized that this should gracefully fall back to the saved post's type
property. Nice 👍
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.
About that, I think we should move the type
out of the edits
state. I don't think it should be stored there since it's not something editable. I would store this on the ui
subtree, the same way we store the current edited ID. Maybe another PR
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.
It is an edit in that it needs to be sent with the initial payload for a new draft of non-post types. Though you're right it's not really editable for saved items and posts of type "post".
return action.postId; | ||
case POST_SAVE_SUCCESS: | ||
return state === action.postId ? action.savedPost.ID : 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.
I guess we need this condition since other posts could potentially be saved in the background that aren't related to the one we're editing? Seems okay enough, since we're effectively ensuring that the ID is kept in sync after a save (especially for draft -> saved 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.
Also, in theory, we could switch the editor's post before the SAVE_SUCCESS is dispatched. but very unlikely to happen.
* @param {String} postType Post Type | ||
* @return {Object} Action object | ||
*/ | ||
export function startEditingPost( siteId, postId, postType ) { |
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.
Do you think we should default postType = 'post'
? Fine to not, though "post" is something of a default in WordPress (see wp_insert_post
, WP_Query
post_type
arguments).
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.
Yeah, I'm all for more consistency between APIs.
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 tests well for me across a variety of types, statuses, and flows. I'm happier with the recent changes; also considering whether this would be a more reliable mechanism for the "Resume Editing" feature (we do similar resetting for this after a save completes).
Code looks good too 👍
If we create a new post, switch to a draft from the sidebar without saving and coming back to /post/new again, some initial post edits were kept instead of having a fresh new post
bcfc667
to
f8f19dd
Compare
@@ -177,6 +175,15 @@ module.exports = { | |||
renderEditor( context, postType ); | |||
}, | |||
|
|||
exitPost: function( context, next ) { | |||
const postId = getPostID( context ); | |||
const siteId = getSelectedSiteId( context.store.getState() ); |
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 seeing a few errors here on the Calypso errors dashboard:
Cannot read property 'getState' of undefined
It's not clear to me when or why context.store
would be ever be undefined
.
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.
Maybe it's because https://github.com/Automattic/wp-calypso/blob/master/client/state/initial-state.js#L90 is async. It seems that the context is initialized on the callback of this function https://github.com/Automattic/wp-calypso/blob/master/client/boot/index.js#L260
Is this the only place we have this kind of errors? seems surprising to me
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.
@youknowriad Following the logic from boot, I don't think we render anything until the initial state has finished loading. It may have something to do with it being a page.exit
handler, which we don't use in very many (any?) other parts of Calypso.
If we create a new post, switch to a draft from the sidebar without saving and coming back to /post/new again, some initial post edits were kept instead of having a fresh new post. Currently, we can experience this for the "sticky" post flag for example.
While we continue reduxifying the editor, this behaviour will be more and more troublesome. (see #8814 )
This PR resets the postEdits while switching posts on the PostEditor Component.
Testing instructions