Skip to content

Commit

Permalink
Implemented hook for validated state (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms authored Aug 22, 2023
1 parent a6baae4 commit e963214
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

import { CaseInfo_api, EnsembleInfo_api, FieldInfo_api } from "@api";
import { CaseInfo_api, EnsembleInfo_api } from "@api";
import { apiService } from "@framework/ApiService";
import { CheckIcon, PlusIcon, TrashIcon } from "@heroicons/react/20/solid";
import { ApiStateWrapper } from "@lib/components/ApiStateWrapper";
Expand All @@ -11,6 +11,7 @@ import { Dropdown } from "@lib/components/Dropdown";
import { IconButton } from "@lib/components/IconButton";
import { Label } from "@lib/components/Label";
import { Select } from "@lib/components/Select";
import { useValidState } from "@lib/hooks/useValidState";
import { useQuery } from "@tanstack/react-query";

import { isEqual } from "lodash";
Expand All @@ -31,9 +32,6 @@ const CACHE_TIME = 5 * 60 * 1000;

export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (props) => {
const [confirmCancel, setConfirmCancel] = React.useState<boolean>(false);
const [selectedField, setSelectedField] = React.useState<string>("");
const [selectedCaseId, setSelectedCaseId] = React.useState<string>("");
const [selectedEnsembleName, setSelectedEnsembleName] = React.useState<string>("");
const [newlySelectedEnsembles, setNewlySelectedEnsembles] = React.useState<EnsembleItem[]>([]);

React.useLayoutEffect(() => {
Expand All @@ -47,36 +45,50 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop
},
});

const computedFieldIdentifier = fixupFieldIdentifier(selectedField, fieldsQuery.data);
const [selectedField, setSelectedField] = useValidState<string>(
"",
[fieldsQuery.data ?? [], (item) => item.field_identifier],
true
);

const casesQuery = useQuery({
queryKey: ["getCases", computedFieldIdentifier],
queryKey: ["getCases", selectedField],
queryFn: () => {
if (!computedFieldIdentifier) {
if (!selectedField) {
return Promise.resolve<CaseInfo_api[]>([]);
}
return apiService.explore.getCases(computedFieldIdentifier);
return apiService.explore.getCases(selectedField);
},
enabled: fieldsQuery.isSuccess,
cacheTime: CACHE_TIME,
staleTime: STALE_TIME,
});

const computedCaseUuid = fixupCaseUuid(selectedCaseId, casesQuery.data);
const [selectedCaseId, setSelectedCaseId] = useValidState<string>(
"",
[casesQuery.data ?? [], (item) => item.uuid],
true
);

const ensemblesQuery = useQuery({
queryKey: ["getEnsembles", computedCaseUuid],
queryKey: ["getEnsembles", selectedCaseId],
queryFn: () => {
if (!computedCaseUuid) {
if (!selectedCaseId) {
return Promise.resolve<EnsembleInfo_api[]>([]);
}
return apiService.explore.getEnsembles(computedCaseUuid);
return apiService.explore.getEnsembles(selectedCaseId);
},
enabled: casesQuery.isSuccess,
cacheTime: CACHE_TIME,
staleTime: STALE_TIME,
});

const [selectedEnsembleName, setSelectedEnsembleName] = useValidState<string>(
"",
[ensemblesQuery.data ?? [], (el) => el.name],
true
);

function handleFieldChanged(fieldIdentifier: string) {
setSelectedField(fieldIdentifier);
}
Expand All @@ -89,13 +101,11 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop
setSelectedEnsembleName(ensembleNames[0]);
}

const computedEnsembleName = fixupEnsembleName(selectedEnsembleName, ensemblesQuery.data);

function checkIfEnsembleAlreadySelected(): boolean {
if (computedCaseUuid && computedEnsembleName) {
if (selectedCaseId && selectedEnsembleName) {
if (
newlySelectedEnsembles.some(
(e) => e.caseUuid === computedCaseUuid && e.ensembleName === computedEnsembleName
(e) => e.caseUuid === selectedCaseId && e.ensembleName === selectedEnsembleName
)
) {
return true;
Expand All @@ -106,8 +116,8 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop

function handleAddEnsemble() {
if (!checkIfEnsembleAlreadySelected()) {
const caseName = casesQuery.data?.find((c) => c.uuid === computedCaseUuid)?.name ?? "UNKNOWN";
const ensArr = [{ caseUuid: computedCaseUuid, caseName: caseName, ensembleName: computedEnsembleName }];
const caseName = casesQuery.data?.find((c) => c.uuid === selectedCaseId)?.name ?? "UNKNOWN";
const ensArr = [{ caseUuid: selectedCaseId, caseName: caseName, ensembleName: selectedEnsembleName }];
setNewlySelectedEnsembles((prev) => [...prev, ...ensArr]);
}
}
Expand Down Expand Up @@ -175,7 +185,7 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop
>
<Dropdown
options={fieldOpts}
value={computedFieldIdentifier}
value={selectedField}
onChange={handleFieldChanged}
disabled={fieldOpts.length === 0}
/>
Expand All @@ -189,7 +199,7 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop
>
<Select
options={caseOpts}
value={[computedCaseUuid]}
value={[selectedCaseId]}
onChange={handleCaseChanged}
disabled={caseOpts.length === 0}
size={5}
Expand All @@ -206,7 +216,7 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop
>
<Select
options={ensembleOpts}
value={[computedEnsembleName]}
value={[selectedEnsembleName]}
onChange={handleEnsembleChanged}
disabled={caseOpts.length === 0}
size={5}
Expand Down Expand Up @@ -306,42 +316,3 @@ export const SelectEnsemblesDialog: React.FC<SelectEnsemblesDialogProps> = (prop
</>
);
};

function fixupFieldIdentifier(currFieldIdentifier: string, fieldArr: FieldInfo_api[] | undefined): string {
const fieldIdentifiers = fieldArr ? fieldArr.map((item) => item.field_identifier) : [];
if (currFieldIdentifier && fieldIdentifiers.includes(currFieldIdentifier)) {
return currFieldIdentifier;
}

if (fieldIdentifiers.length > 0) {
return fieldIdentifiers[0];
}

return "";
}

function fixupCaseUuid(currCaseUuid: string, caseArr: CaseInfo_api[] | undefined): string {
const caseIds = caseArr ? caseArr.map((item) => item.uuid) : [];
if (currCaseUuid && caseIds.includes(currCaseUuid)) {
return currCaseUuid;
}

if (caseIds.length > 0) {
return caseIds[0];
}

return "";
}

function fixupEnsembleName(currEnsembleName: string, ensembleArr: EnsembleInfo_api[] | undefined): string {
const ensembleNames = ensembleArr ? ensembleArr.map((item) => item.name) : [];
if (currEnsembleName && ensembleNames.includes(currEnsembleName)) {
return currEnsembleName;
}

if (ensembleNames.length > 0) {
return ensembleNames[0];
}

return "";
}
41 changes: 41 additions & 0 deletions frontend/src/lib/hooks/useValidState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";

export function useValidState<T, K = any>(
initialState: T | (() => T),
validStates: readonly T[] | [readonly K[], (element: K) => T],
keepStateWhenInvalid = true
): [T, (newState: T | ((prevState: T) => T), acceptInvalidState?: boolean) => void] {
const [state, setState] = React.useState<T>(initialState);

let validState = state;
const computedInitialState = typeof initialState === "function" ? (initialState as () => T)() : initialState;

let adjustedValidStates: T[] = [];
if (validStates.length === 2 && Array.isArray(validStates[0]) && typeof validStates[1] === "function") {
adjustedValidStates = validStates[0].map(validStates[1] as (element: K) => T);
} else {
adjustedValidStates = validStates as T[];
}

if (!adjustedValidStates.includes(state)) {
if (adjustedValidStates.length > 0) {
validState = adjustedValidStates[0];
} else {
validState = computedInitialState;
}
if (!keepStateWhenInvalid) {
setState(validState);
}
}

function setValidState(newState: T | ((prevState: T) => T), acceptInvalidState = true) {
const computedNewState = typeof newState === "function" ? (newState as (prevState: T) => T)(state) : newState;
if (!acceptInvalidState && !adjustedValidStates.includes(computedNewState)) {
return;
}

setState(newState);
}

return [validState, setValidState];
}

0 comments on commit e963214

Please sign in to comment.