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
Improve Console extension React hook & HOC #4211
Conversation
Reselect (
|
/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(), |
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.
Would be still useful to provide a reusable selector 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.
Will do.
export const stateToFlagsObject = (state: FeatureState, desiredFlags: string[]): FlagsObject => | ||
desiredFlags.reduce((allFlags, f) => ({ ...allFlags, [f]: state.get(f) }), {} as FlagsObject); |
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.
support desiredFlags
as option so we can continue to use this selector as previously implemented.
If no desiredFlags
supplied, fallback to all flags.
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 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 😃
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 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, |
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.
can we avoid the use of any
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.
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.
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 a bug in the typing of defaultMemoize
.
No problem keep it.
const gatingFlagSelectorCreator = React.useMemo( | ||
() => | ||
createSelectorCreator( | ||
defaultMemoize as any, |
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 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>({ |
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.
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(), |
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.
Will do.
export const stateToFlagsObject = (state: FeatureState, desiredFlags: string[]): FlagsObject => | ||
desiredFlags.reduce((allFlags, f) => ({ ...allFlags, [f]: state.get(f) }), {} as FlagsObject); |
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 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 😃
4cf4c7c
to
5755800
Compare
/lgtm |
[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 |
/retest Please review the full test history for this PR and help us cut down flakes. |
2 similar comments
/retest Please review the full test history for this PR and help us cut down flakes. |
/retest Please review the full test history for this PR and help us cut down flakes. |
/retest |
@vojtechszocs: The following test failed, say
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. |
/retest |
This PR includes the following improvements:
withExtensions
parameter change)useExtensions
not memoizing its result properly (gatingFlags
was always recomputed)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)cc @christianvogt @spadgett @benjaminapetersen @rawagner