-
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: Use Redux state for Post Formats #8593
Conversation
52f14c6
to
0749739
Compare
/cc @aduth |
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.
+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 | ||
} ) ) |
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 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 ?
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 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.
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 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.
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.
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.
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 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 )
.
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, 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).
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.
Now, all this being said, these are things that we can incrementally refactor toward 😄
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 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 😄
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 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'; |
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.
Good catch, fixing now.
Thanks for the review @youknowriad! Feel free to let me know if you have any other suggestions. |
return ( | ||
<PostFormatsData siteId={ this.props.site.ID }> | ||
<div> | ||
<QueryPostFormats siteId={ this.props.site.ID } /> |
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.
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.
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.
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 | ||
} ) ) |
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 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 | ||
} ) ) |
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, 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 | ||
} ) ) |
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.
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 | ||
} ) ) |
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 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 😄
0530461
to
079fb19
Compare
@aduth I'd appreciate if you could take a final look before we merge this one. |
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 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 ) ) { |
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.
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)
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.
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 } /> |
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 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).
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.
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 ) => { |
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 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';
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.
Makes sense, now's a good time to simplify this logic.
|
||
export default React.createClass( { | ||
const EditorPostFormats = React.createClass( { | ||
displayName: 'EditorPostFormats', |
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.
displayName
becomes unnecessary when you assign React.createClass
to a variable (via babel-plugin-transform-react-disdplay-name
).
} ); | ||
|
||
if ( isEmpty( this.props.postFormats ) ) { |
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.
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:
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.
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'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.
if ( ! this.props.post || ! this.props.postFormats ) { | ||
return this.translate( 'Loading…' ); | ||
if ( ! post || ! postFormats ) { | ||
return null; |
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.
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
.
icon={ <Gridicon icon="types" /> } | ||
className={ classes }> | ||
<PostFormats | ||
post={ 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.
Doesn't appear we use this prop in <PostFormats />
?
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.
Curious. We weren't using it with the previous Flux implementation as well. Omitting that.
Looks like this needs a final review @aduth. |
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.
LGTM 👍
b5de842
to
bbff785
Compare
… 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.
… 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.
This PR is a continuation of #8402, which migrated the legacy
lib/post-formats
Flux store to Redux. This PR:PostFormatsStore
Flux store.PostFormatsData
data component.To test:
http://calypso.localhost:3000/post/$site/
, where$site
is a site supports one or more additional post formats.