-
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
Themes: Use Selectors instead of Helpers #6924
Themes: Use Selectors instead of Helpers #6924
Conversation
aed54a7
to
53bd88d
Compare
998338e
to
2a1ca9a
Compare
b5a4a7c
to
8ccbd57
Compare
Hi @ockham Checking in on this older pull request. Still in progress? |
Stalled right now, will probably reboot after our current milestone is done. |
2c4830e
to
4a12b3d
Compare
4a12b3d
to
96cac05
Compare
02c40fd
to
be74822
Compare
be74822
to
78d4fef
Compare
8911e20
to
9e27889
Compare
9e27889
to
961fd34
Compare
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.
Looks good, just a couple of questions.
Tested in particular:
- Current site bar when switching sites
- Options for various showcase pages
- Activating a theme from 'all sites' page
// defaultOption prop. Otherwise, there will be an error that will prevent the | ||
// state update from finishing properly, hence not updating defaultOption at all. | ||
// The solution to this incredibly intricate issue is simple: Give ThemeSheet | ||
// a valid defaultProp for defaultOption. |
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 is a pretty long comment. Should maybe be in the docs instead?
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.
Sounds like a good idea. Which ones, though? 😄
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.
Heh, good question. I was thinking client/my-sites/theme/README.md. Or at least make it a multiline comment.
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.
Part of it is quite general (the stuff about this being a react-redux
quirk), but since the rest is really quite specific to the code here (much more than general README notes), I'm inclined to leave it here. Will turn it into a multi-line /* comment */
tho.
} | ||
export default ( props ) => ( | ||
<MultiSiteThemeShowcase { ...props } | ||
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 much easier to see what the options will be for the different showcase pages 👍
|
||
export const connectOptions = connect( | ||
( state, { options: optionNames, site } ) => { | ||
let options = pick( ALL_THEME_OPTIONS, optionNames ); |
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.
what does this line give us over using the passed in options directly?
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 idea is that individual theme option objects -- used separately from connectOptions
-- almost don't make any sense at all, since they'd always need to be bound at least to state
(and dispatch
, in case of e.g. activate
-- and install
and delete
, later on), and sometimes to siteId
. So I'm thinking of option objects more of an implementation detail right now (the interface being connectOptions
). I should probably stop export
ing 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.
(Expanding on that, we could consider turning those strings into constants so that one would have to import { THEME_OPTION_ACTIVATE } from 'theme-options';
but so far, i haven't quite felt like typing so much...)
@@ -62,10 +61,6 @@ const Theme = React.createClass( { | |||
actionLabel: React.PropTypes.string | |||
}, | |||
|
|||
shouldComponentUpdate( nextProps ) { | |||
return ! isEqual( nextProps.theme, this.props.theme ); |
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 came in as an optimization #2915
Is it safe to remove?
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 main rationale for this is that we now have to re-render if the buttonOptions
prop is changed, which will happen e.g. if a different theme is activated: In some of the options' hideForTheme
predicates, we're using the isActiveTheme
selector. So if those predicates change, we definitely want to re-render, or a newly activated theme will have the wrong items in its '...' menu. The same holds in principle true for the screenshotClickUrl
and onScreenshotClick
.
I think we mostly need to make sure that buttonOptions
don't change unnecessarily, and possibly by adding back shouldComponentUpdate
, with a deep comparison (i.e. another isEqual
) of buttonOptions
alongside the one for theme
, plus some comparisons for the stuff I mentioned above (screenshotClickUrl
, onScreenshotClick
, etc).
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 we mostly need to make sure that buttonOptions don't change unnecessarily, and possibly by adding back shouldComponentUpdate, with a deep comparison (i.e. another isEqual) of buttonOptions alongside the one for theme, plus some comparisons for the stuff I mentioned above (screenshotClickUrl, onScreenshotClick, etc).
In this PR or a separate one? I don't want to hold this one up—on the other hand I'm loath to introduce a performance regression.
@@ -63,7 +62,12 @@ const Theme = React.createClass( { | |||
}, | |||
|
|||
shouldComponentUpdate( nextProps ) { | |||
return ! isEqual( nextProps.theme, this.props.theme ); | |||
// TODO: Once we're not using theme.active and theme.purchased anymore, just compare theme.id instead of entire theme objects. | |||
return ! isEqual( this.props.theme, this.props.theme ) || |
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.props.theme
twice here
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.
Argh. Good catch, ty!
Add a `connectOptions` helper function that bundles all binding to state and site (if given) in one place for greater ease-of-use, and have it use selectors instead of helpers for `getUrl` attrs. Furthermore, use (newly written) `isActiveTheme` selector in theme options' `hideForTheme` predicates. The latter feature is particularly relevant as it is the first (and hardest) step to decoupling theme objects from contextual data, and using selectors like `isActiveTheme` that take state, site ID, and theme as arguments instead tying that information to theme object (as in `theme.active`). This will allow us to keep only one list of all wpcom themes that just contains bare, i.e. non-contextual, theme information, without relying on attrs like `.active` or `.purchased`, that we can use both in multi- and single-site mode (instead of multiplicating that list for all sites a user has). We can't use the `isThemePurchased` selector yet as `getSitePurchases` (the selector it relies on) seems to be buggy -- it doesn't always return the same results. We'll need to do a separate PR -- the code changes to use that selector in `hideForTheme` should be fairly easy, most of the effort will need to go into researching why and how the underlying selector is broken. This PR also removes `shouldComponentUpdate`s in the `Theme` and `ThemesList` components. This is required so those components also update if `buttonOptions` change, which, as of this PR, will happen e.g. if a theme is activated, since `ThemeOptions` now makes use of the `isActiveTheme` selector inside `hideForTheme`. Note that I had to add a new `isActive` selector which relies on the existing `current-theme` Redux subtree instead of the `isThemeActive` selector that relies on a given site's `options.theme_slug` attr (and which I hoped would allow us to stop tracking active themes per site in a separate subtree). However, there's a rather tricky bug with `options.theme_slug` which is documented in a comment in the source code, so I'm afraid we'll have to keep our own `current-theme` subtree around for the time being (though we should refactor it to be in line with the new `themes` state tree structure). No visual changes. To test: Make sure that the theme showcase still works as before (compare to `master` or production). * Since this is an invasive change, it requires extensive testing in all modes (logged-out, multi-site, single-site [for both wpcom and Jetpack sites], and theme sheets) * Testing should cover: * Verifying that a theme thumbnail's ellipsis menu contains all the right items, and that they all work as expected. * Verifying that call-to-action buttons in a theme sheet, and in theme preview are present, present the correct action, and are correctly labelled.
4181ad1
to
7e78fe5
Compare
Add a `connectOptions` helper function that bundles all binding to state and site (if given) in one place for greater ease-of-use, and have it use selectors instead of helpers for `getUrl` attrs. Furthermore, use (newly written) `isActiveTheme` selector in theme options' `hideForTheme` predicates. The latter feature is particularly relevant as it is the first (and hardest) step to decoupling theme objects from contextual data, and using selectors like `isActiveTheme` that take state, site ID, and theme as arguments instead tying that information to theme object (as in `theme.active`). This will allow us to keep only one list of all wpcom themes that just contains bare, i.e. non-contextual, theme information, without relying on attrs like `.active` or `.purchased`, that we can use both in multi- and single-site mode (instead of multiplicating that list for all sites a user has). We can't use the `isThemePurchased` selector yet as `getSitePurchases` (the selector it relies on) seems to be buggy -- it doesn't always return the same results. We'll need to do a separate PR -- the code changes to use that selector in `hideForTheme` should be fairly easy, most of the effort will need to go into researching why and how the underlying selector is broken. This PR also removes `shouldComponentUpdate`s in the `Theme` and `ThemesList` components. This is required so those components also update if `buttonOptions` change, which, as of this PR, will happen e.g. if a theme is activated, since `ThemeOptions` now makes use of the `isActiveTheme` selector inside `hideForTheme`. Note that I had to add a new `isActive` selector which relies on the existing `current-theme` Redux subtree instead of the `isThemeActive` selector that relies on a given site's `options.theme_slug` attr (and which I hoped would allow us to stop tracking active themes per site in a separate subtree). However, there's a rather tricky bug with `options.theme_slug` which is documented in a comment in the source code, so I'm afraid we'll have to keep our own `current-theme` subtree around for the time being (though we should refactor it to be in line with the new `themes` state tree structure). No visual changes. To test: Make sure that the theme showcase still works as before (compare to `master` or production). * Since this is an invasive change, it requires extensive testing in all modes (logged-out, multi-site, single-site [for both wpcom and Jetpack sites], and theme sheets) * Testing should cover: * Verifying that a theme thumbnail's ellipsis menu contains all the right items, and that they all work as expected. * Verifying that call-to-action buttons in a theme sheet, and in theme preview are present, present the correct action, and are correctly labelled.
Add a
connectOptions
helper function that bundles all binding to state and site (if given) in one place for greater ease-of-use, and have it use selectors instead of helpers forgetUrl
attrs. Furthermore, use (newly written)isActiveTheme
selector in theme options'hideForTheme
predicates.The latter feature is particularly relevant as it is the first (and hardest) step to decoupling theme objects from contextual data, and using selectors like
isActiveTheme
that take state, site ID, and theme as arguments instead tying that information to theme object (as intheme.active
). This will allow us to keep only one list of all wpcom themes that just contains bare, i.e. non-contextual, theme information, without relying on attrs like.active
or.purchased
, that we can use both in multi- and single-site mode (instead of multiplicating that list for all sites a user has).We can't use the
isThemePurchased
selector yet asgetSitePurchases
(the selector it relies on) seems to be buggy -- it doesn't always return the same results. We'll need to do a separate PR -- the code changes to use that selector inhideForTheme
should be fairly easy, most of the effort will need to go into researching why and how the underlying selector is broken.This PR also removes
shouldComponentUpdate
s in theTheme
andThemesList
components. This is required so those components also update ifbuttonOptions
change, which, as of this PR, will happen e.g. if a theme is activated, sinceThemeOptions
now makes use of theisActiveTheme
selector insidehideForTheme
.Note that I had to add a new
isActive
selector which relies on the existingcurrent-theme
Redux subtree instead of theisThemeActive
selector that relies on a given site'soptions.theme_slug
attr (and which I hoped would allow us to stop tracking active themes per site in a separate subtree). However, there's a rather tricky bug withoptions.theme_slug
which is documented in a comment in the source code, so I'm afraid we'll have to keep our owncurrent-theme
subtree around for the time being (though we should refactor it to be in line with the newthemes
state tree structure). cc @budzanowskiNo visual changes. To test:
Make sure that the theme showcase still works as before (compare to
master
or production).Next step -- next PR:
Replace all remaining occurrences of
theme.active
by the newisActiveTheme
selector. Figure out what to do about theTheme
component.TODO for later PRs:
themeID
instead of an entire theme object (and have them obtain other required theme attributes as needed). (Possibly same forsite
->siteId
)get...Url()
helpers by selectors.isPremiumTheme
a selectorwindow
dependencytheme-options
, move tolib/
orcomponents/
.getCustomizeUrl( state, siteId )
alias forgetThemeCustomizeUrl( state, null, siteId )
?helpers.js
(once all usages have been replaced by selectors)Supersedes #6165.
Test live: https://calypso.live/?branch=update/themes-turn-helpers-into-selectors-take-two