Skip to content

Commit

Permalink
feat: some preloading (#1003)
Browse files Browse the repository at this point in the history
  • Loading branch information
marudor authored Jan 13, 2025
1 parent 3bb2691 commit 7d5c7dd
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 125 deletions.
216 changes: 100 additions & 116 deletions src/client/Common/Components/StopPlaceSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCommonConfig } from '@/client/Common/provider/CommonConfigProvider';
import type { AllowedHafasProfile } from '@/types/HAFAS';
import type { MinimalStopPlace } from '@/types/stopPlace';
import { MenuItem, Paper, TextField, styled } from '@mui/material';
import Downshift from 'downshift';
import { useCombobox } from 'downshift';
import { useCallback, useRef } from 'react';
import type { ChangeEventHandler, FC, FocusEventHandler } from 'react';
import { Loading, LoadingType } from './Loading';
Expand Down Expand Up @@ -43,7 +43,7 @@ export interface Props {

export const StopPlaceSearch: FC<Props> = ({
id,
onChange,
onChange: stopPlaceOnChange,
value,
autoFocus,
placeholder,
Expand All @@ -65,129 +65,113 @@ export const StopPlaceSearch: FC<Props> = ({
[showRl100],
);

const {
suggestions,
setSuggestions,
loading,
loadOptions,
itemToString,
selectRef,
} = useStopPlaceSearch({
filterForIris,
maxSuggestions,
groupedBySales,
});
const { suggestions, setSuggestions, loading, loadOptions, itemToString } =
useStopPlaceSearch({
filterForIris,
maxSuggestions,
groupedBySales,
});

const downshiftOnChange = useCallback(
(stopPlace: MinimalStopPlace | null) => {
({
selectedItem: stopPlace,
}: { selectedItem: MinimalStopPlace | null }) => {
inputRef.current?.blur();
onChange(stopPlace || undefined);
stopPlaceOnChange(stopPlace || undefined);
},
[onChange],
[stopPlaceOnChange],
);

const {
getItemProps,
getLabelProps,
getMenuProps,
getInputProps,
highlightedIndex,
inputValue,
isOpen,
openMenu,
setInputValue,
} = useCombobox({
id,
items: suggestions,
defaultHighlightedIndex: 0,
selectedItem: value || null,
itemToString,
onSelectedItemChange: downshiftOnChange,
});

const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({
onChange: ((event) => {
void loadOptions(event.target.value);
}) as ChangeEventHandler<HTMLInputElement>,
onFocus: (() => {
if (value && value.name === inputValue) {
setInputValue('');
}
if (suggestions.length) {
openMenu();
}
}) as FocusEventHandler<HTMLInputElement>,
onBlur: (() => {
setSuggestions([]);
if (value) {
setInputValue(value.name);
}
}) as FocusEventHandler<HTMLInputElement>,
placeholder,
autoFocus,
ref: inputRef,
});

return (
<Container>
<Downshift
id={id}
defaultHighlightedIndex={0}
// @ts-expect-error ???
ref={selectRef}
selectedItem={value || null}
itemToString={itemToString}
onChange={downshiftOnChange}
>
{({
clearSelection,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
setState,
openMenu,
}) => {
const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({
onChange: ((event) => {
if (event.target.value === '') {
clearSelection();
} else {
void loadOptions(event.target.value);
}
}) as ChangeEventHandler<HTMLInputElement>,
onFocus: (() => {
if (value && value.name === inputValue) {
setState({ inputValue: '' });
}
if (suggestions.length) {
openMenu();
}
}) as FocusEventHandler<HTMLInputElement>,
onBlur: (() => {
setSuggestions([]);
if (value) {
setState({ inputValue: value.name });
}
}) as FocusEventHandler<HTMLInputElement>,
placeholder,
autoFocus,
ref: inputRef,
});

return (
<div data-testid={id}>
<TextField
fullWidth
slotProps={{
inputLabel: getLabelProps({ shrink: true }),
input: {
onBlur,
onChange,
onFocus,
},
htmlInput: {
...inputProps,
'data-testid': 'stopPlaceSearchInput',
},
}}
/>
<div {...getMenuProps()}>
{isOpen && (
<SuggestionContainer square>
{suggestions.length ? (
suggestions.map((suggestion, index) => {
const itemProps = getItemProps({
item: suggestion,
index,
});
const highlighted = highlightedIndex === index;
<div data-testid={id}>
<TextField
fullWidth
slotProps={{
inputLabel: getLabelProps({ shrink: true }),
input: {
onBlur,
onChange,
onFocus,
},
htmlInput: {
...inputProps,
'data-testid': 'stopPlaceSearchInput',
},
}}
/>
<div {...getMenuProps()}>
{isOpen && (
<SuggestionContainer square>
{suggestions.length ? (
suggestions.map((suggestion, index) => {
const itemProps = getItemProps({
item: suggestion,
index,
});
const highlighted = highlightedIndex === index;

return (
<MenuItem
data-testid="stopPlaceSearchMenuItem"
{...itemProps}
key={suggestion.evaNumber}
selected={highlighted}
component="div"
>
{formatSuggestion(suggestion)}
</MenuItem>
);
})
) : (
<MenuItem>
{loading ? 'Loading...' : 'No options'}
</MenuItem>
)}
</SuggestionContainer>
)}
</div>
</div>
);
}}
</Downshift>
return (
<MenuItem
data-testid="stopPlaceSearchMenuItem"
{...itemProps}
key={suggestion.evaNumber}
selected={highlighted}
component="div"
>
{formatSuggestion(suggestion)}
</MenuItem>
);
})
) : (
<MenuItem>{loading ? 'Loading...' : 'No options'}</MenuItem>
)}
</SuggestionContainer>
)}
</div>
</div>
{loading && <PositionedLoading type={LoadingType.dots} />}
</Container>
);
Expand Down
13 changes: 7 additions & 6 deletions src/client/Common/hooks/useStopPlaceSearch.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { trpc } from '@/router';
import type { MinimalStopPlace } from '@/types/stopPlace';
import debounce from 'debounce-promise';
import type { ControllerStateAndHelpers } from 'downshift';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';

interface UseStationSearchOptions {
interface UseStopPlaceSearchOptions {
maxSuggestions: number;
filterForIris?: boolean;
groupedBySales?: boolean;
Expand All @@ -15,7 +14,7 @@ const itemToString = (s: MinimalStopPlace | null) => (s ? s.name : '');
export const useStopPlaceSearch = ({
filterForIris,
maxSuggestions,
}: UseStationSearchOptions) => {
}: UseStopPlaceSearchOptions) => {
const [suggestions, setSuggestions] = useState<MinimalStopPlace[]>([]);
const [loading, setLoading] = useState(false);
const trpcUtils = trpc.useUtils();
Expand All @@ -36,6 +35,10 @@ export const useStopPlaceSearch = ({

const loadOptions = useCallback(
async (value: string) => {
if (!value) {
setSuggestions([]);
return;
}
setLoading(true);

const currentSuggestions = await stopPlaceFn(value);
Expand All @@ -45,14 +48,12 @@ export const useStopPlaceSearch = ({
},
[stopPlaceFn],
);
const selectRef = useRef<ControllerStateAndHelpers<MinimalStopPlace>>(null);

return {
loadOptions,
suggestions,
setSuggestions,
loading,
itemToString,
selectRef,
};
};
11 changes: 9 additions & 2 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export const trpcClient = trpc.createClient({
const queryClientOptions: QueryClientConfig = {
defaultOptions: {
queries: {
staleTime: 3000,
staleTime(query) {
// @ts-expect-error ugly but wqorks
switch (query.queryKey?.[0]?.[0]) {
case 'stopPlace':
return Number.POSITIVE_INFINITY;
}
return 3000;
},
retry: 0,
refetchOnWindowFocus: false,
},
Expand Down Expand Up @@ -91,7 +98,6 @@ export function createRouter(request?: Request) {

return createReactRouter({
context: {
fullUrl: request?.url || window.location.href,
baseUrl: request?.url ? new URL(request.url).host : window.location.host,
trpcUtils,
},
Expand All @@ -110,6 +116,7 @@ export function createRouter(request?: Request) {
dehydrate: () => ({
queryClientState: dehydrate(queryClient),
}),
defaultPreload: 'intent',
});
}

Expand Down
1 change: 0 additions & 1 deletion src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ window.$RefreshSig$ = () => (type) => type`,
let plausibleScript: (typeof scripts)[number] | undefined;

export const Route = createRootRouteWithContext<{
fullUrl: string;
baseUrl: string;
trpcUtils: TRPCQueryUtilsType;
}>()({
Expand Down

0 comments on commit 7d5c7dd

Please sign in to comment.