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

Improve Console extension React hook & HOC #4211

Merged

Conversation

vojtechszocs
Copy link
Contributor

This PR includes the following improvements:

useExtensions result value can now be used as a "stable" dependency of other hooks:

  • React.useMemo
  • React.useEffect (this one also throws if one of its dependencies changes on every render)
  • etc.

cc @christianvogt @spadgett @benjaminapetersen @rawagner

@openshift-ci-robot openshift-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Feb 5, 2020
@vojtechszocs
Copy link
Contributor Author

Reselect (createSelectorCreator) + React-Redux (useSelector) approach to memoizing a value computed from Redux state is based on @rawagner's code (feels a bit complex but works perfectly):

const k8sModelSelectorCreator = React.useMemo(

@openshift-ci-robot openshift-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. component/core Related to console core functionality component/sdk Related to console-plugin-sdk labels Feb 5, 2020
@vojtechszocs
Copy link
Contributor Author

/retest

@@ -104,7 +104,7 @@ const NavHeader_: React.FC<NavHeaderProps & StateProps> = ({

const mapStateToProps = (state: RootState): StateProps => ({
activePerspective: getActivePerspective(state),
flags: stateToFlagsObject(state),
flags: state[featureReducerName].toObject(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be still useful to provide a reusable selector here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

Comment on lines +97 to +98
export const stateToFlagsObject = (state: FeatureState, desiredFlags: string[]): FlagsObject =>
desiredFlags.reduce((allFlags, f) => ({ ...allFlags, [f]: state.get(f) }), {} as FlagsObject);
Copy link
Contributor

Choose a reason for hiding this comment

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

support desiredFlags as option so we can continue to use this selector as previously implemented.
If no desiredFlags supplied, fallback to all flags.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can do that, but we should also differentiate between "empty flags" (empty array) vs. "all flags" (falsy value) cases.

I'll make desiredFlags optional and fallback to null which would allow us to reuse it like before 😃

Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't realize this function was using FeatureState.
This is change request was related to #4211 (comment) where it would have been nice to keep a selector.

const gatingFlagSelectorCreator = React.useMemo(
() =>
createSelectorCreator(
defaultMemoize as any,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we avoid the use of any here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So far, neither @rawagner nor I found a way to avoid it, see also #3443 (comment).

TypeScript "test" for Reselect contains the following code (link):

createSelectorCreator(defaultMemoize, <T>(a: T, b: T, index: number) => {
  if (index === 0)
    return a === b;

  return a.toString() === b.toString();
});

So I've tried to adapt it to our code.

My 1st iteration - doesn't work, since T in <T>(a: T, b: T) => boolean is basically any object:

createSelectorCreator(
  defaultMemoize,
  <FeatureState>(prevFeatureState: FeatureState, nextFeatureState: FeatureState) =>
    // TS error: property 'get' does not exist on 'FeatureState'
    gatingFlagNames.every((f) => prevFeatureState.get(f) === nextFeatureState.get(f)),
)

My 2nd iteration - doesn't work as well:

createSelectorCreator(
  defaultMemoize,
  <T extends FeatureState>(prevFeatureState: T, nextFeatureState: T) =>
    // TS error: 'T' is not assignable to 'FeatureState'
    gatingFlagNames.every((f) => prevFeatureState.get(f) === nextFeatureState.get(f)),
)

It seems that Reselect typings for createSelectorCreator API aren't robust enough.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a bug in the typing of defaultMemoize.
No problem keep it.

const gatingFlagSelectorCreator = React.useMemo(
() =>
createSelectorCreator(
defaultMemoize as any,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So far, neither @rawagner nor I found a way to avoid it, see also #3443 (comment).

TypeScript "test" for Reselect contains the following code (link):

createSelectorCreator(defaultMemoize, <T>(a: T, b: T, index: number) => {
  if (index === 0)
    return a === b;

  return a.toString() === b.toString();
});

So I've tried to adapt it to our code.

My 1st iteration - doesn't work, since T in <T>(a: T, b: T) => boolean is basically any object:

createSelectorCreator(
  defaultMemoize,
  <FeatureState>(prevFeatureState: FeatureState, nextFeatureState: FeatureState) =>
    // TS error: property 'get' does not exist on 'FeatureState'
    gatingFlagNames.every((f) => prevFeatureState.get(f) === nextFeatureState.get(f)),
)

My 2nd iteration - doesn't work as well:

createSelectorCreator(
  defaultMemoize,
  <T extends FeatureState>(prevFeatureState: T, nextFeatureState: T) =>
    // TS error: 'T' is not assignable to 'FeatureState'
    gatingFlagNames.every((f) => prevFeatureState.get(f) === nextFeatureState.get(f)),
)

It seems that Reselect typings for createSelectorCreator API aren't robust enough.

isNavItem,
isPerspective,
)(
withExtensions<NavSectionExtensionProps>({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed before, this type param shouldn't be necessary, but I'd do that as a follow-up improvement.

That said, the withExtensions HOC is currently used on a single place, hopefully that number stays low.

@@ -104,7 +104,7 @@ const NavHeader_: React.FC<NavHeaderProps & StateProps> = ({

const mapStateToProps = (state: RootState): StateProps => ({
activePerspective: getActivePerspective(state),
flags: stateToFlagsObject(state),
flags: state[featureReducerName].toObject(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

Comment on lines +97 to +98
export const stateToFlagsObject = (state: FeatureState, desiredFlags: string[]): FlagsObject =>
desiredFlags.reduce((allFlags, f) => ({ ...allFlags, [f]: state.get(f) }), {} as FlagsObject);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can do that, but we should also differentiate between "empty flags" (empty array) vs. "all flags" (falsy value) cases.

I'll make desiredFlags optional and fallback to null which would allow us to reuse it like before 😃

@christianvogt
Copy link
Contributor

/lgtm

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Mar 5, 2020
@openshift-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: christianvogt, vojtechszocs

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

2 similar comments
@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

@openshift-bot
Copy link
Contributor

/retest

Please review the full test history for this PR and help us cut down flakes.

@vojtechszocs
Copy link
Contributor Author

/retest

@openshift-ci-robot
Copy link
Contributor

openshift-ci-robot commented Mar 6, 2020

@vojtechszocs: The following test failed, say /retest to rerun all failed tests:

Test name Commit Details Rerun command
ci/prow/verify 4cf4c7c link /test verify

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@vojtechszocs
Copy link
Contributor Author

/retest

@openshift-merge-robot openshift-merge-robot merged commit 4c022bf into openshift:master Mar 6, 2020
@spadgett spadgett added this to the v4.5 milestone Mar 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. component/core Related to console core functionality component/sdk Related to console-plugin-sdk lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants