-
Notifications
You must be signed in to change notification settings - Fork 43
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
refactor(RHICOMPL-1105): contextual useFeature hook #827
Conversation
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.
Nice refactoring! It continues to work as expected, and the warnings have disappeared. I'll defer to @bastilian for a more detailed code review, but the new context seems really nice :)
} | ||
}; | ||
|
||
export const EnabledFeatures = ({ children }) => { |
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.
💯
useEffect(() => { | ||
let setFeatures = {}; | ||
|
||
const urlParams = new URLSearchParams(search || ''); |
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 is one difference with the previous code, and that is redirection. In this context-based version there is no redirection (the passed parameters will stay in location/URL until navigation). We can add it if you think it is needed.
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 redirect was/is intended.
There are other URL dependent features on the platform that we should avoid to interfere with.
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.
Sure, I can add it.
Currently (in CI) any URL query string does a redirect. I guess we already broke that interfere promise. 😉
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.
Currently (in CI) any URL query string does a redirect. I guess we already broke that interfere promise. 😉
Write
I'm not sure if that is actually a bug, but I'd need to (re)investigate that. I believe this is correct behaviour of react-router. If no component renders it will redirect. I think. For now there don't seem to be issues with that.
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 redirects, because the original useFeature
does not check available features, sets anything to local storage, and then redirects back (clearing query string). react-router does not impact it.
I believe that query string isn't used by react-router in our app.
}; | ||
|
||
export const EnabledFeatures = ({ children }) => { | ||
const [enabledFeatures, setEnabledFeatures] = useState(getStoredFeatures); |
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 might not need to keep this in a state. It would only give us a benefit if we'd have a way to set the feature flags other than via the URL.
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.
Partially true, but a state does not hurt. We have to realize that the useEffect (defined down) can happen asynchronously, hence the state.
Further enhancements are also possible, like live feature views.
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 will never need this.
|
||
const getStoredFeatures = () => { | ||
let savedState = { ...features }; | ||
Object.keys(features).forEach((feature) => { |
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.
Prefer .map
& spread over .forEach
& X[Y] = Z
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 you please write an example?
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 function could for example read like this:
const getStoredFeatures = () => {
const featuresEnabled = Object.assign(...Object.keys(features).map((feature) => (
{ [feature]: !!localStorage.getItem(`${LOCAL_STORE_FEATURE_PREFIX}:${feature}`) || false }
)).filter((v) => !!v));
return {
...features,
...featuresEnabled
};
};
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.
Sorry, but I don't find it more readable.
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.
Using forEach
and assigning properties in an object is an obsolete way of doing this.
We aim for JavaScript written towards ES6(+), and therefore should make use of .map
(and other functions) and utilise spreading.
If you look around other places in this codebase as well as other projects in the Insights organisation you'll see that it is a common (and encouraged) pattern.
const [enabledFeatures, setEnabledFeatures] = useState(getStoredFeatures); | ||
const { search } = useLocation(); | ||
|
||
useEffect(() => { |
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 seems to just reimplement what was the useFeature
hook.
Why not try to preserve most of the original hook and make it "context aware" and simply invoke it 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.
The implementation move from the hook here is the whole point of this. The hook would have to access a context an would have to be able to write to it. The problem of the hook was that each time it was used, the same was parsed over and over again. There was no global state, which this new context adds.
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 implementation should be in a hook.
The point of using hooks is to keep Component bodies small and make them less complex.
useFeature hook now uses global EnabledFeaturesContext. This improves: * parsing URL only once * setting and checking stored values only once * globally accessible context through useFeature hook
3151662
to
5ecb65a
Compare
Codecov Report
@@ Coverage Diff @@
## master #827 +/- ##
==========================================
- Coverage 77.65% 77.30% -0.36%
==========================================
Files 65 66 +1
Lines 971 978 +7
Branches 138 139 +1
==========================================
+ Hits 754 756 +2
- Misses 186 188 +2
- Partials 31 34 +3
Continue to review full report at Codecov.
|
|
||
})(); | ||
|
||
global.localStorageMock = localStorageMock; |
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.
@bastilian Is this the right way to do global mocks?
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.
You can also only mock it in the tests themselves using jest.mock
.
Setting up a global mock shouldn't be needed, since only one set of tests interacts with it.
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 not sure if jest allows to mock globals, as per the issue jestjs/jest#2098 (comment)
There is also a package to help with the localStorage (which I've skipped) https://www.npmjs.com/package/jest-localstorage-mock
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.
In test you should not mock a global, you'd mock useFeature
and for the tests of hook related code useLocation
.
Converted to draft, after discussion with @bastilian. There are concerns about the implementation with a global context, that might possibly impact next deployment. |
@vkrizan @bastilian it sounds like there were some fundamental disagreements on this one. Should we continue to try and get it merged or close it? |
Ok! So we can close this one? @vkrizan? |
useFeature hook now uses global EnabledFeaturesContext.
This improves:
Also fixes:
reportsTableView
constant (typo)