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

Editor: Use Redux state for Post Formats #8593

Merged
merged 10 commits into from
Oct 13, 2016

Conversation

tyxla
Copy link
Member

@tyxla tyxla commented Oct 7, 2016

This PR is a continuation of #8402, which migrated the legacy lib/post-formats Flux store to Redux. This PR:

  • Modifies the post formats to use the new Redux state
  • ES6ifies the editor post formats components
  • Fixes several ESLint warnings.
  • Removes the unused legacy PostFormatsStore Flux store.
  • Removes the unused legacy PostFormatsData data component.

To test:

  • Checkout this branch
  • Go to http://calypso.localhost:3000/post/$site/, where $site is a site supports one or more additional post formats.
  • Create a new post with a different post format and save it.
  • Open the edit page of the newly created post.
  • In all of the above cases, play around with the post formats and verify they work as expected (change, save and retrieve accordingly).

@tyxla tyxla added [Feature] Post/Page Editor The editor for editing posts and pages. [Status] In Progress State labels Oct 7, 2016
@tyxla tyxla self-assigned this Oct 7, 2016
@matticbot
Copy link
Contributor

@tyxla tyxla force-pushed the update/post-formats-use-redux-state branch from 52f14c6 to 0749739 Compare October 10, 2016 12:47
@tyxla
Copy link
Member Author

tyxla commented Oct 10, 2016

/cc @aduth

@tyxla tyxla added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] In Progress labels Oct 10, 2016
Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

+84/-441 This is awesome! I look forward deleting all these Flux stores :). The code looks good, aside from the error I've got.

postFormats: React.PropTypes.arrayOf( React.PropTypes.shape( {
slug: React.PropTypes.string,
label: React.PropTypes.string
} ) )
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose the change of the postFormats type is due to the return value of the getPostFormats selector. I was wondering whether we should add something like Object.values(postFormats) inside the selector to get an array instead of an object.

I know the object can have better performance when accessing postFormats by slug, but it feels more "natural" to have an array of postFormats when we call a function called getPostFormats, don't you think ?

Copy link
Member Author

Choose a reason for hiding this comment

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

it feels more "natural" to have an array of postFormats when we call a function called getPostFormats, don't you think ?

I don't actually 😃 That's why I converted it to an object in the Redux version. I really don't understand why it would be better to have that as an array. Array here just adds additional overhead, while object helps us simplify the code for managing the formats and accessing them.

Copy link
Contributor

Choose a reason for hiding this comment

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

I respect your point of view and It's a good compromise for me If you want to keep this part as is. but I can explain more my point of view.

I think storing it as an object in Redux is great, because we can write performant selectors based on this. We can write getPostFormats selector which we could call if we want to have the full list (array) while we can write another selector getPostFormat(state, slug) to access directly the required post format by Slug. Maybe I would also add another selector called getPostFormatsBySlug which returns the object by slug.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for sharing your view. It's always beneficial to hear another opinion. 👍

And regarding the getPostFormatBySlug / getPostFormat selector - I was thinking about writing one, but it wont be used anywhere, so we can live without it at this time. And considering that post formats are not a feature that is really evolving in the WordPress core, I doubt we'll expand our need for additional post format selectors anytime soon.

Copy link
Contributor

@aduth aduth Oct 10, 2016

Choose a reason for hiding this comment

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

I'm fairly certain I've implemented selectors with both of these return value options in the past, and while it depends on the UI requirements, I'd probably side more with @youknowriad on this one, which is that:

  • The reducer shape tracks an object of post formats, keyed by slug
  • The selectors transform this to whatever is most appropriate for the UI

I think it's rare that the UI requirements would dictate returning the original object from the selector.

Here, I see two requirements:

  • Showing the label of the selected format in the accordion subtitle
  • Showing a list of all post format options

To the first, we could achieve this with a selector getPostFormat( state, siteId, slug ) to retrieve the single post format object label (instead of trying to look it up from the entire set as has been done 'til now, which can be error prone and is nice to have a tested selector for).

To the second, since it's used as a single unit (list), an array seems to be the most appropriate return value from getPostFormats( state, siteId ).

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, tying this to my earlier comment, if we separate the requirements of the accordion and accordion contents where each has its own connect selector, you'll see that the distinction becomes very clear that getPostFormat for the current selected post value is most appropriate for the <EditorPostFormatsAccordion /> component and the getPostFormats for the <EditorPostFormats /> component (to list options).

Copy link
Contributor

Choose a reason for hiding this comment

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

Now, all this being said, these are things that we can incrementally refactor toward 😄

Copy link
Contributor

@aduth aduth Oct 10, 2016

Choose a reason for hiding this comment

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

I realized pretty late in reviewing this that the object structure of the reducer is slug to label string, not slug to an object of properties, and while that makes it a little less obvious, I'd still stand by my previous remarks. Interesting discussion in any case 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree we can build up such changes incrementally - personally I'm not seeing any blockers here. After all the main purpose of this PR is to transfer the existing post formats components to Redux - further improvements can be done in subsequent PRs. But please feel free to correct me if you feel I'm wrong @aduth.

import FormRadio from 'components/forms/form-radio';
import Gridicon from 'components/gridicon';
import PostActions from 'lib/posts/actions';
import stats from 'lib/posts/stats';
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed an error while testing.

capture d ecran 2016-10-10 a 2 44 11 pm

I think the fix is something like import * as stats from 'lib/posts/stats' here. or better import only the functions you need recordEvent ect..

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, fixing now.

@youknowriad youknowriad added [Status] Needs Author Reply and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Oct 10, 2016
@tyxla
Copy link
Member Author

tyxla commented Oct 10, 2016

Thanks for the review @youknowriad! Feel free to let me know if you have any other suggestions.

@tyxla tyxla added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] Needs Author Reply labels Oct 10, 2016
@youknowriad youknowriad added [Status] Ready to Merge and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Oct 10, 2016
return (
<PostFormatsData siteId={ this.props.site.ID }>
<div>
<QueryPostFormats siteId={ this.props.site.ID } />
Copy link
Contributor

@aduth aduth Oct 10, 2016

Choose a reason for hiding this comment

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

We should strive for components to be self-sufficient with query components and connect applied as close to the UI requirements as possible. To that point, at the very least it should be the <EditorPostFormatsAccordion /> component which renders the <QueryPostFormats /> and retrieves postFormats from state. I might go one step further and say that both <EditorPostFormatsAccordion /> and <EditorPostFormats /> should separately connect to pull from postFormats state so that they're not so tightly coupled.

In the end, I'd love if <EditorDrawer /> were simply a render function like:

function EditorDrawer() {
    return (
        <div className="editor-drawer">
            <EditorDrawerTaxonomies />
            <EditorDrawerFeaturedImage />
            <EditorDrawerPageOptions />
            <EditorDrawerSharing />
            <EditorDrawerPostFormats />
            <EditorDrawerSeo />
            <EditorDrawerMoreOptions />
        </div>
    );
}

Where each component is responsible for deciding itself whether to render and how.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point. I've now decoupled both <EditorPostFormats /> and <EditorPostFormatsAccordion />, so they separately connect and pull from the postFormats state. A good pro of this is that we no longer need to call the query component within the editor drawer.

postFormats: React.PropTypes.arrayOf( React.PropTypes.shape( {
slug: React.PropTypes.string,
label: React.PropTypes.string
} ) )
Copy link
Contributor

@aduth aduth Oct 10, 2016

Choose a reason for hiding this comment

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

I'm fairly certain I've implemented selectors with both of these return value options in the past, and while it depends on the UI requirements, I'd probably side more with @youknowriad on this one, which is that:

  • The reducer shape tracks an object of post formats, keyed by slug
  • The selectors transform this to whatever is most appropriate for the UI

I think it's rare that the UI requirements would dictate returning the original object from the selector.

Here, I see two requirements:

  • Showing the label of the selected format in the accordion subtitle
  • Showing a list of all post format options

To the first, we could achieve this with a selector getPostFormat( state, siteId, slug ) to retrieve the single post format object label (instead of trying to look it up from the entire set as has been done 'til now, which can be error prone and is nice to have a tested selector for).

To the second, since it's used as a single unit (list), an array seems to be the most appropriate return value from getPostFormats( state, siteId ).

postFormats: React.PropTypes.arrayOf( React.PropTypes.shape( {
slug: React.PropTypes.string,
label: React.PropTypes.string
} ) )
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, tying this to my earlier comment, if we separate the requirements of the accordion and accordion contents where each has its own connect selector, you'll see that the distinction becomes very clear that getPostFormat for the current selected post value is most appropriate for the <EditorPostFormatsAccordion /> component and the getPostFormats for the <EditorPostFormats /> component (to list options).

postFormats: React.PropTypes.arrayOf( React.PropTypes.shape( {
slug: React.PropTypes.string,
label: React.PropTypes.string
} ) )
Copy link
Contributor

Choose a reason for hiding this comment

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

Now, all this being said, these are things that we can incrementally refactor toward 😄

postFormats: React.PropTypes.arrayOf( React.PropTypes.shape( {
slug: React.PropTypes.string,
label: React.PropTypes.string
} ) )
Copy link
Contributor

@aduth aduth Oct 10, 2016

Choose a reason for hiding this comment

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

I realized pretty late in reviewing this that the object structure of the reducer is slug to label string, not slug to an object of properties, and while that makes it a little less obvious, I'd still stand by my previous remarks. Interesting discussion in any case 😄

@tyxla tyxla force-pushed the update/post-formats-use-redux-state branch from 0530461 to 079fb19 Compare October 11, 2016 07:22
@tyxla tyxla added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] Ready to Merge labels Oct 11, 2016
@tyxla
Copy link
Member Author

tyxla commented Oct 11, 2016

@aduth I'd appreciate if you could take a final look before we merge this one.

Copy link
Contributor

@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'm nitpicking at this point. It's looking really great, no blockers from me, but a few minor suggestions. Feel free to address or punt to a future PR. Otherwise good to go 👍

} );

if ( isEmpty( this.props.postFormats ) ) {
if ( isEmpty( postFormats ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For another pull request, but noting it here, we should try to render the accordion even when data is not yet available (Reactivity and Loading States)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, that's a good point. And I think this PR is the right place for that - with a clear redux state the user will never reach the post formats query component, so the post formats might never load. Fixed and thanks for noticing 👍

@@ -72,10 +72,23 @@ module.exports = React.createClass( {
subtitle={ this.getSubtitle() }
icon={ <Gridicon icon="types" /> }
className={ classes }>
<QueryPostFormats siteId={ this.props.site.ID } />
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a blocker, but we'll want to move to eliminate the site object as a prop, so it'd be better to pass the siteId from getSelectedSiteId in as a prop (even if duplicating from site for now).

Copy link
Member Author

Choose a reason for hiding this comment

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

Done - the post formats components will now use the siteId, which they fetch from the state. The only downside is that we still need the site object in EditorPostFormatsAccordion because siteUtils.getDefaultPostFormat uses a site object. But we can refactor that in a future PR.

const { value } = this.props;

if ( 'standard' === value ) {
return 'standard';
}

const isSupportedFormat = this.getPostFormats().some( ( postFormat ) => {
return postFormat.slug === value;
const isSupportedFormat = some( this.getPostFormats(), ( postFormatLabel, postFormatSlug ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

If getPostFormats returns an object keyed by slug, why not express as:

const isSupportedFormat = this.getPostFormats().hasOwnProperty( value );
// Or: const isSupportedFormat = !! this.getPostFormats()[ value ];
// Or: return this.getPostFormats()[ value ] || 'standard';

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, now's a good time to simplify this logic.

@aduth aduth added [Status] Ready to Merge and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Oct 11, 2016
@tyxla tyxla added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] Ready to Merge labels Oct 12, 2016

export default React.createClass( {
const EditorPostFormats = React.createClass( {
displayName: 'EditorPostFormats',
Copy link
Contributor

Choose a reason for hiding this comment

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

displayName becomes unnecessary when you assign React.createClass to a variable (via babel-plugin-transform-react-disdplay-name).

} );

if ( isEmpty( this.props.postFormats ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So I found a subtle regression by this change. It appears that previously we were using this to account for themes which define theme support for post-formats but don't define any custom post formats. I notice for the Confit theme, for example, it has post-formats support but the /sites/%s/post-formats endpoint returns an empty object. This leads to a strange user experience where we show the accordion but there's only a single option:

image

In master, we'd have avoided the accordion being rendered if there was only one of these options.

So then the question becomes: Do we show the accordion by default, and only hide it after we know that either the theme doesn't support it or there are no custom post formats? Or do we hide it by default and show it only when we know there are custom post formats?

It probably depends on what's more common across themes, since it's more-or-less equally jarring for a control to appear as it is to disappear. Judgment call on your part.

But we should preserve the original behavior, which is to hide it if there are no post formats returned from the endpoint.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've restored the previous behavior of hiding the post formats accordion if there are no post formats, but it will also call the query component in any case, so the post formats will actually be loaded.

@aduth aduth added [Status] Needs Author Reply and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Oct 12, 2016
if ( ! this.props.post || ! this.props.postFormats ) {
return this.translate( 'Loading…' );
if ( ! post || ! postFormats ) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't need to change, but FYI a child of a component can be undefined to render nothing, so this could be simplified to return; . Only in the case of the return value of render does it need to be null.

@tyxla tyxla added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] Needs Author Reply labels Oct 12, 2016
icon={ <Gridicon icon="types" /> }
className={ classes }>
<PostFormats
post={ post }
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't appear we use this prop in <PostFormats /> ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Curious. We weren't using it with the previous Flux implementation as well. Omitting that.

@tyxla
Copy link
Member Author

tyxla commented Oct 12, 2016

Looks like this needs a final review @aduth.

Copy link
Contributor

@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.

LGTM 👍

@aduth aduth added [Status] Ready to Merge and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Oct 12, 2016
@tyxla tyxla force-pushed the update/post-formats-use-redux-state branch from b5de842 to bbff785 Compare October 13, 2016 06:47
@tyxla tyxla merged commit 51a8e1f into master Oct 13, 2016
@tyxla tyxla deleted the update/post-formats-use-redux-state branch October 13, 2016 06:57
jsnajdr added a commit that referenced this pull request May 23, 2018
… accordion

The Post Format Accordion does its own check for null `post` or `siteId` and
doesn't render anything in that case: it waits until the `postFormats` for the
site and post type are fully known.

There's no need to check the `post` prop in the `EditorDrawer` component. This patch
removes that check, which is one of the last remaining usages of the Flux-based post
object.

I also do a little cleanup of the Post Formats Accordion, namely removing the `isLoading`
prop. As the component doesn't render anything at all until all data are available, it's
redundant. The prop is a leftover from earlier refactoring in #8593.
jsnajdr added a commit that referenced this pull request May 24, 2018
… accordion

The Post Format Accordion does its own check for null `post` or `siteId` and
doesn't render anything in that case: it waits until the `postFormats` for the
site and post type are fully known.

There's no need to check the `post` prop in the `EditorDrawer` component. This patch
removes that check, which is one of the last remaining usages of the Flux-based post
object.

I also do a little cleanup of the Post Formats Accordion, namely removing the `isLoading`
prop. As the component doesn't render anything at all until all data are available, it's
redundant. The prop is a leftover from earlier refactoring in #8593.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Post/Page Editor The editor for editing posts and pages. State
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants