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

refactor(RHICOMPL-1105): contextual useFeature hook #827

Closed

Conversation

vkrizan
Copy link
Collaborator

@vkrizan vkrizan commented Nov 13, 2020

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

Also fixes:

  • warnings on non-boolean value passed from a stored value
  • reportsTableView constant (typo)

@vkrizan vkrizan requested a review from bastilian November 13, 2020 18:25
Copy link
Contributor

@akofink akofink left a 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 }) => {
Copy link
Contributor

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 || '');
Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Collaborator Author

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. 😉

Copy link
Member

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.

Copy link
Collaborator Author

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);
Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

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) => {
Copy link
Member

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

Copy link
Collaborator Author

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?

Copy link
Member

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
    };
};

Copy link
Collaborator Author

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.

Copy link
Member

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(() => {
Copy link
Member

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?

Copy link
Collaborator Author

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.

Copy link
Member

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
@vkrizan vkrizan force-pushed the improve-use-feature-1105 branch from 3151662 to 5ecb65a Compare November 16, 2020 12:45
@codecov-io
Copy link

Codecov Report

Merging #827 (5ecb65a) into master (689f458) will decrease coverage by 0.35%.
The diff coverage is 54.54%.

Impacted file tree graph

@@            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     
Impacted Files Coverage Δ
src/constants.js 55.00% <ø> (ø)
src/Utilities/EnabledFeatures.js 51.61% <51.61%> (ø)
src/Utilities/hooks/useFeature.js 100.00% <100.00%> (+37.03%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 689f458...5ecb65a. Read the comment docs.


})();

global.localStorageMock = localStorageMock;
Copy link
Collaborator Author

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?

Copy link
Member

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.

Copy link
Collaborator Author

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

Copy link
Member

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.

@vkrizan vkrizan marked this pull request as draft November 16, 2020 16:39
@vkrizan
Copy link
Collaborator Author

vkrizan commented Nov 16, 2020

Converted to draft, after discussion with @bastilian. There are concerns about the implementation with a global context, that might possibly impact next deployment.

@akofink
Copy link
Contributor

akofink commented Mar 11, 2021

@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?

@bastilian
Copy link
Member

bastilian commented Mar 12, 2021

@akofink The main objective of this was to do parsing of URL as well as only setting and checking stored values only once, this has meanwhile been addressed when correcting the hook to comply with the linter[1].

[1] 7a46666

@akofink
Copy link
Contributor

akofink commented Mar 12, 2021

Ok! So we can close this one? @vkrizan?

@vkrizan vkrizan closed this Mar 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants