Skip to content

Commit

Permalink
fix bug, that switching tabs leads to autocomplete label beeing null
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Tilsch committed Jan 22, 2024
1 parent 7036154 commit 0c7e860
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 117 deletions.
112 changes: 53 additions & 59 deletions apps/exhibition-live/components/form/DebouncedAutoComplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import React, {
} from "react";

import { TextField } from "./TextField";
import { useQuery } from "@tanstack/react-query";

export type AutocompleteSuggestion = {
label: string;
Expand All @@ -19,6 +20,7 @@ export type AutocompleteSuggestion = {

export type DebouncedAutocompleteProps = {
load: (value?: string) => Promise<AutocompleteSuggestion[]>;
initialQueryKey?: string;
placeholder: string;
minSearchLength?: number;
loadOnStart?: boolean;
Expand All @@ -33,7 +35,7 @@ export type DebouncedAutocompleteProps = {
"renderInput" | "size" | "options"
>;

const emptySuggetions: AutocompleteSuggestion[] = [
const emptySuggestions: AutocompleteSuggestion[] = [
{
label: "",
value: null,
Expand All @@ -43,6 +45,7 @@ export const DebouncedAutocomplete: FunctionComponent<
DebouncedAutocompleteProps
> = ({
load,
initialQueryKey,
title,
minSearchLength = 1,
loadOnStart,
Expand All @@ -52,24 +55,23 @@ export const DebouncedAutocomplete: FunctionComponent<
onDebouncedSearchChange,
condensed,
inputProps,
value,
...props
}) => {
const [suggestions, setSuggestions] = useState<
AutocompleteSuggestion[] | undefined
>(emptySuggetions);
>(emptySuggestions);
const [loading, setLoading] = useState<boolean>(false);

const [initiallyLoaded, setInitiallyLoaded] = useState(false);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedRequest = useCallback(
debounce(async (value: string) => {
const data = await load(value);
onDebouncedSearchChange && onDebouncedSearchChange(value);
if (data.length > 0) {
setSuggestions([...data, ...emptySuggetions]);
setSuggestions([...data, ...emptySuggestions]);
} else {
setSuggestions(emptySuggetions);
setSuggestions(emptySuggestions);
}
setLoading(false);
}, 500),
Expand All @@ -87,63 +89,55 @@ export const DebouncedAutocomplete: FunctionComponent<
},
[setLoading, debouncedRequest, minSearchLength, onSearchValueChange],
);
const { data: initialData, isLoading } = useQuery(
["initiallyLoadSuggestions", initialQueryKey],
() => load(),
{
enabled: Boolean(initialQueryKey && loadOnStart && ready),
},
);

useEffect(() => {
if (loadOnStart && ready && !initiallyLoaded) {
setLoading(true);
load().then((data) => {
if (data?.length > 0) {
setSuggestions([...data, ...emptySuggetions]);
}
setLoading(false);
setInitiallyLoaded(true);
});
if (initialData?.length > 0) {
setSuggestions([...initialData, ...emptySuggestions]);
}
}, [
setLoading,
setSuggestions,
load,
initiallyLoaded,
setInitiallyLoaded,
loadOnStart,
ready,
]);
}, [initialData, setSuggestions]);

return (
<>
<Autocomplete
noOptionsText="No results"
readOnly={readOnly}
{...props}
renderInput={(params) => (
// @ts-ignore
<TextField
{...params}
label={condensed ? undefined : title}
variant={"standard"}
placeholder={condensed ? title : props.placeholder}
onChange={handleOnChange}
InputProps={{
...params.InputProps,
disabled: readOnly,
startAdornment: (
<>
{loading ? (
<CircularProgress color="inherit" size={16} />
) : readOnly ? (
<Link fontSize="small" />
) : (
<Search fontSize="small" />
)}
{params.InputProps.startAdornment}
</>
),
}}
{...inputProps}
/>
)}
options={suggestions}
/>
</>
<Autocomplete
noOptionsText="No results"
readOnly={readOnly}
isOptionEqualToValue={(option, value) => option.value === value.value}
value={value}
{...props}
renderInput={(params) => (
// @ts-ignore
<TextField
{...params}
label={condensed ? undefined : title}
variant={"standard"}
placeholder={condensed ? title : props.placeholder}
onChange={handleOnChange}
InputProps={{
...params.InputProps,
disabled: readOnly,
startAdornment: (
<>
{isLoading || loading ? (
<CircularProgress color="inherit" size={16} />
) : readOnly ? (
<Link fontSize="small" />
) : (
<Search fontSize="small" />
)}
{params.InputProps.startAdornment}
</>
),
}}
{...inputProps}
/>
)}
options={suggestions}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { TextFieldProps, useControlled } from "@mui/material";
import parse from "html-react-parser";
import React, {
FunctionComponent,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import React, { FunctionComponent, useCallback } from "react";

import { useGlobalCRUDOptions } from "../../state/useGlobalCRUDOptions";
import { findEntityByClass } from "../../utils/discover";
import {
AutocompleteSuggestion,
DebouncedAutocomplete,
} from "../DebouncedAutoComplete";
import { loadEntityBasics } from "../../utils/crud/loadEntityBasics";
import { defaultPrefix, sladb } from "../formConfigs";
import { useQuery } from "@tanstack/react-query";

interface OwnProps {
selected?: AutocompleteSuggestion | null;
Expand All @@ -36,14 +33,14 @@ interface OwnProps {
type Props = OwnProps;

const DiscoverAutocompleteInput: FunctionComponent<Props> = ({
title = "etwas",
title = "",
typeName,
readonly,
defaultSelected,
selected,
onEnterSearch,
onSelectionChange,
typeIRI: classType,
typeIRI,
loadOnStart,
limit,
onDebouncedSearchChange,
Expand All @@ -53,14 +50,15 @@ const DiscoverAutocompleteInput: FunctionComponent<Props> = ({
searchString: searchStringProp,
}) => {
const { crudOptions } = useGlobalCRUDOptions();
const [selectedValue, setSelectedUncontrolled] = useControlled({
name: "DiscoverAutocompleteInput",
controlled: selected,
default: defaultSelected || null,
});
const [selectedValue, setSelectedUncontrolled] =
useControlled<AutocompleteSuggestion | null>({
name: "DiscoverAutocompleteInput-selected",
controlled: selected,
default: defaultSelected || null,
});

const [searchString, setSearchString] = useControlled<string | undefined>({
name: "DiscoverAutocompleteInput",
name: "DiscoverAutocompleteInput-searchString",
controlled: searchStringProp,
default: "",
});
Expand All @@ -83,11 +81,11 @@ const DiscoverAutocompleteInput: FunctionComponent<Props> = ({

const load = useCallback(
async (searchString?: string) =>
classType && crudOptions
typeIRI && crudOptions
? (
await findEntityByClass(
searchString || null,
classType,
typeIRI,
crudOptions.selectFetch,
limit,
)
Expand All @@ -98,7 +96,28 @@ const DiscoverAutocompleteInput: FunctionComponent<Props> = ({
};
})
: [],
[classType, crudOptions, limit],
[typeIRI, crudOptions, limit],
);

const { data: basicFields } = useQuery(
["loadEntity", selected?.value, typeName],
async () => {
const value = selected?.value;
if (value && crudOptions && crudOptions.selectFetch) {
const typeIRI = sladb(typeName).value;
return await loadEntityBasics(value, typeIRI, crudOptions.selectFetch, {
defaultPrefix: defaultPrefix,
});
}
return null;
},
{
enabled: Boolean(
crudOptions?.selectFetch &&
typeof selected?.value === "string" &&
(!selected?.label || selected?.label?.length === 0),
),
},
);

const handleEnter = useCallback(
Expand All @@ -118,33 +137,40 @@ const DiscoverAutocompleteInput: FunctionComponent<Props> = ({
[onSearchValueChange, setSearchString],
);

const handleGetOptionLabel = useCallback(
(option: AutocompleteSuggestion) => {
return option.label || basicFields?.label || option.value || "";
},
[basicFields],
);

return (
<>
<DebouncedAutocomplete
title={title}
readOnly={readonly}
loadOnStart={true}
ready={Boolean(classType && crudOptions)}
// @ts-ignore
load={load}
value={selectedValue || null}
placeholder={`Suche nach ${typeName} in der aktuellen Datenbank`}
renderOption={(props, option: any) => (
<li {...props} key={option.value}>
{parse(
`<span class="debounced_autocomplete_option_label">${option.label}</span>`,
)}
</li>
)}
// @ts-ignore
onChange={handleChange}
onDebouncedSearchChange={onDebouncedSearchChange}
condensed={condensed}
onKeyUp={handleEnter}
onSearchValueChange={handleSearchValueChange}
inputProps={inputProps}
/>
</>
<DebouncedAutocomplete
title={title}
readOnly={readonly}
loadOnStart={true}
ready={Boolean(typeIRI && crudOptions)}
// @ts-ignore
load={load}
initialQueryKey={typeIRI}
value={selectedValue || { label: "", value: null }}
getOptionLabel={handleGetOptionLabel}
placeholder={`Suche nach ${typeName} in der aktuellen Datenbank`}
renderOption={(props, option: any) => (
<li {...props} key={option.value}>
{parse(
`<span class="debounced_autocomplete_option_label">${option.label}</span>`,
)}
</li>
)}
// @ts-ignore
onChange={handleChange}
onDebouncedSearchChange={onDebouncedSearchChange}
condensed={condensed}
onKeyUp={handleEnter}
onSearchValueChange={handleSearchValueChange}
inputProps={inputProps}
/>
);
};

Expand Down
2 changes: 1 addition & 1 deletion apps/exhibition-live/components/renderer/ArrayToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export const ArrayLayoutToolbar = memo(
typeName={typeName}
title={label || ""}
onEnterSearch={handleCreateNewFromSearch}
searchString={searchString}
searchString={searchString || ""}
onSearchValueChange={handleSearchStringChange}
onSelectionChange={handleSelectedChange}
inputProps={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ const InlineCondensedSemanticFormsRenderer = (props: ControlProps) => {
[config?.formsPath, path],
);
const selected = useMemo(
() => ({ value: data || null, label: realLabel }),
() =>
data
? { value: data || null, label: realLabel }
: { value: null, label: null },
[data, realLabel],
);
const { $ref, typeIRI } = uischema.options?.context || {};
Expand Down Expand Up @@ -104,12 +107,11 @@ const InlineCondensedSemanticFormsRenderer = (props: ControlProps) => {
path.substring(0, path.length - ("@id".length + 1)),
);
const fieldDecl = primaryFields[typeName] as PrimaryField | undefined;
let label = data;
let label = "";
if (fieldDecl?.label)
label = extractFieldIfString(parentData, fieldDecl.label);
if (typeof label === "object") {
console.warn("label is object", label);
return JSON.stringify(label);
return "";
}
return label;
});
Expand Down Expand Up @@ -256,7 +258,7 @@ const InlineCondensedSemanticFormsRenderer = (props: ControlProps) => {
selected={selected}
onSelectionChange={handleSelectedChange}
onSearchValueChange={handleSearchStringChange}
searchString={searchString}
searchString={searchString || ""}
inputProps={{
onFocus: handleFocus,
}}
Expand Down
Loading

0 comments on commit 0c7e860

Please sign in to comment.