Skip to content

Conversation

dolbeew
Copy link
Collaborator

@dolbeew dolbeew commented May 4, 2022

Converts the DatasetPage component from a class to a function component that uses React hooks


PR checklist

  • This PR meets the Acceptance Criteria in the JIRA story
  • The JIRA story has been moved to Dev Review
  • This PR includes appropriate unit tests
  • I have added explanatory comments where the logic is not obvious
  • I have run and tested this change locally, and my testing process is described here
  • If this includes a new feature flag, I have created and linked new JIRA tickets to (a) turn on the feature flag and (b) remove it later
  • If this includes an API change, I have run the E2E tests on this change against my local server with yarn test-local because this PR won't be covered by the CircleCI tests
  • If this includes a UI change, I have taken screen recordings or screenshots of the new behavior and notified the PO and UX designer in Slack
  • If this change impacts deployment safety (e.g. removing/altering APIs which are in use) I have documented these in the description
  • If this includes an API change, I have updated the appropriate Swagger definitions and updated the appropriate API consumers

@dolbeew dolbeew requested a review from calbach May 5, 2022 15:33
Copy link
Contributor

@calbach calbach left a comment

Choose a reason for hiding this comment

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

Sorry for the delay. There is a lot going on here and it's difficult for me to tease apart what is changing with this PR vs just moving around. I will probably have to do another pass over the whole thing only looking at the new code, which might generate a bunch more comments on existing issues. But for now I think I have a few high level ones for your consideration.

Thanks for taking this on.

showErrorModal,
workspace,
}: Props) => {
let dt: any;
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems suspect, seems like React.Component or JSX.ELement or a similar type should work here (maybe check what type is provided in the ref callback below). A more specific name would definitely be good as well, given how far this definition is from where it's used e.g. dataTableComponent

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ended up removing this. The only purpose for it was to check if text for column headers overlapped and needed a tooltip, which was complicated and wasn't actually working anyway.

Added a check for overlapping text when hovering over the column header instead.

Comment on lines 753 to 834
const [cohortList, setCohortList] = useState([]);
const [conceptSetList, setConceptSetList] = useState([]);
const [crossDomainConceptSetList, setCrossDomainConceptSetList] = useState(
new Set()
);
const [dataSet, setDataSet] = useState(undefined);
const [dataSetTouched, setDataSetTouched] = useState(false);
const [domainValueSetIsLoading, setDomainValueSetIsLoading] = useState(
new Set()
);
const [domainValueSetLookup, setDomainValueSetLookup] = useState(new Map());
const [includesAllParticipants, setIncludesAllParticipants] =
useState(false);
const [loadingResources, setLoadingResources] = useState(true);
const [modalState, setModalState] = useState(ModalState.None);
const [previewList, setPreviewList] = useState(new Map());
const [selectedCohortIds, setSelectedCohortIds] = useState([]);
const [selectedConceptSetIds, setSelectedConceptSetIds] = useState([]);
const [selectedDomains, setSelectedDomains] = useState(new Set());
const [
selectedDomainsWithConceptSetIds,
setSelectedDomainsWithConceptSetIds,
] = useState(new Set());
const [selectedPreviewDomain, setSelectedPreviewDomain] = useState(
Domain.CONDITION
);
const [selectedPrepackagedConceptSets, setSelectedPrepackagedConceptSets] =
useState(new Set());
const [selectedDomainValuePairs, setSelectedDomainValuePairs] = useState(
[]
);
const [savingDataset, setSavingDataset] = useState(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

There are a lot of state hooks here. As one quick win: there's a category of state hooks here which are basically derived state / lookup optimizations. These are good to have, but I think we can make this dependency more direct:

const selectedDomains = userMemo(() => ...initialization logic..., [selectedDomainValuePairs]);
const domainValueSetLookup = useMemo(() => ...initialization logic..., [selectedDomainValuePairs]);

I haven't used useMemo extensively, so there might be a cleaner way of doing what I'm suggesting too. Thoughts?

As for the rest of this state, I think as a follow-up we should consider looking for opportunities to merge some of these state variables together into larger chunks, e.g. have a single state hook for the transient dataset object. If you see clear opportunities here, go for it - but I'm not going to make the request here since this is already large.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, there are a lot of hooks here. For the initial conversion, I basically added a state variable for each property in state from the old class component but I'll look at grouping related variables together.

I'll also look at the additional hooks like useMemo as I haven't used them yet, just useState and useEffect so far. Not sure if it will make it in this pr or a follow up.

return new Set(domains);
};

const apiEnumToPrePackageConceptSets = (
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I'd probably pull this out of the render function at least, maybe out to a utils file or something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved further up in the file for now to get it out of the component. Filed RW-8358 since there are other components/functions that can be move from this file also.

v.map((pre) => {
switch (pre) {
case PrePackagedConceptSetEnum.BOTH: {
re.add(PrepackagedConceptSet.PERSON),
Copy link
Contributor

Choose a reason for hiding this comment

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

existing issue, but this seems like a very odd usage of , - I suspect it may have even been a typo for ; originally.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, looks like a typo. Kind of surprised this didn't cause any errors/warnings.

v: Array<PrePackagedConceptSetEnum>
): Set<PrepackagedConceptSet> => {
const re: Set<PrepackagedConceptSet> = new Set<PrepackagedConceptSet>();
v.map((pre) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: existing issue, but forEach would be clearer, since the return value is disregarded 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.

Done.

};

const loadDataset = (dataset: DataSet, initialLoad?: boolean) => {
// This is to set the URL to reference the newly created dataset and the user is staying on the dataset page
Copy link
Contributor

Choose a reason for hiding this comment

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

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 comment in the code here is kind of confusing, but we want to update the url without refreshing the page or remounting the component.

IIUC the purpose of withParamsKey is to force a remount on change of a url param.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're right - I misunderstood the intent here. This seems OK then - there might be a cleaner way of doing it but I'd need to see why remounting is a problem first..

hideSpinner();
updatePrepackagedDomains();

(async () => await loadResources())();
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be equivalent to just calling loadResources(), and not waiting on the return promise. Maybe with a comment explaining that you're letting it run in the background

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, didn't realize on first pass that the async/await here wasn't doing anything, which is an issue when loading a saved dataset. I moved the if statement below into loadResources to make sure all the resources have returned before loading the dataset.

this.loadValueSetForDomain(nd);
});
}
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: for completeness, this should probably list datasetId

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.


useEffect(() => {
updatePrepackagedDomains();
}, [workspace.namespace, workspace.id, cdrVersionTiersResponse]);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit tricky to follow, presumably the callback uses these variables.

A possible alternative you could try:

const updatePrepackagedDomains = useCallback(() => ..., [[workspace.namespace, workspace.id, cdrVersionTiersResponse]);

useEffect(() => updatePrepackagedDomains(), [updatePrepackagedDomains]);

i.e. updatePrepackagedDomains is only recomputed when its dependencies change. The useEffect is only run when updatePrepackagedDomains is recomputed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So I ended up removing this as it doesn't seem necessary. I'm finding a lot of the updates that were in componentDidUpdate were either redundant or never used.

@dolbeew dolbeew force-pushed the dolbeew/RW-7167 branch from 88f2b1d to 003dd77 Compare May 18, 2022 13:23
Copy link
Contributor

@calbach calbach left a comment

Choose a reason for hiding this comment

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

thanks

@dolbeew dolbeew force-pushed the dolbeew/RW-7167 branch from dbd6127 to deb0e12 Compare July 1, 2022 20:34
@dolbeew dolbeew merged commit 3f4335e into main Jul 1, 2022
@dolbeew dolbeew deleted the dolbeew/RW-7167 branch July 1, 2022 20:53
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.

2 participants