Skip to content

Commit 3352778

Browse files
committed
Fix ESLint errors
1 parent 6c37718 commit 3352778

File tree

10 files changed

+101
-96
lines changed

10 files changed

+101
-96
lines changed

src/components/BaseSelect/useHighlightedIndex.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { UseComboboxStateChange, UseSelectStateChange } from "downshift";
2-
import { useEffect, useState } from "react";
2+
import { useMemo, useState } from "react";
33

44
import { Option } from "~/components/BaseSelect";
55

@@ -12,31 +12,33 @@ export function useHighlightedIndex<T extends Option>(
1212
change: UseComboboxStateChange<T> | UseSelectStateChange<T>
1313
) => void;
1414
} {
15-
// Initially we don't show any item as highlighted
16-
const [highlightedIndex, setHighlightedIndex] = useState<number | undefined>(
17-
-1
18-
);
19-
20-
// When data from API comes we can calculate initially highlighted index
21-
// Or when we change the selected item
22-
useEffect(() => {
23-
// If we don't have selected item leave highlighted index as -1
24-
if (!selectedItem || highlightedIndex !== -1) {
25-
return;
26-
}
27-
28-
// Find highlighted index in items to select base on selected item value
29-
// If there is no match, leave highlighted index as -1
30-
setHighlightedIndex(getIndexToHighlight(items, selectedItem));
31-
}, [highlightedIndex, items, selectedItem]);
15+
// Derive highlighted index from items and selectedItem
16+
// This automatically updates when items array changes (e.g., API data arrives)
17+
const derivedHighlightedIndex = useMemo(() => {
18+
if (!selectedItem) return -1;
19+
return getIndexToHighlight(items, selectedItem);
20+
}, [items, selectedItem]);
21+
22+
// Track whether user has interacted with keyboard navigation
23+
// undefined means we should use the derived index
24+
const [userHighlightedIndex, setUserHighlightedIndex] = useState<
25+
number | undefined
26+
>(undefined);
27+
28+
// Use user-set index if available, otherwise use derived index
29+
const highlightedIndex =
30+
userHighlightedIndex !== undefined
31+
? userHighlightedIndex
32+
: derivedHighlightedIndex;
3233

3334
const handleHighlightedIndexChange = ({
34-
highlightedIndex,
35+
highlightedIndex: newIndex,
3536
}: UseComboboxStateChange<T> | UseSelectStateChange<T>) => {
36-
if (selectedItem && highlightedIndex === -1) {
37-
setHighlightedIndex(getIndexToHighlight(items, selectedItem));
37+
if (selectedItem && newIndex === -1) {
38+
// When Downshift resets to -1, fall back to derived index
39+
setUserHighlightedIndex(undefined);
3840
} else {
39-
setHighlightedIndex(highlightedIndex);
41+
setUserHighlightedIndex(newIndex);
4042
}
4143
};
4244

src/components/Combobox/Dynamic/DynamicCombobox.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,22 @@ const DynamicComboboxInner = <T extends Option>(
107107
onBlur,
108108
});
109109

110-
const { refs, floatingStyles } = useFloating<HTMLLabelElement>({
111-
shouldUpdate: isOpen,
112-
});
110+
const { setReferenceRef, setFloatingRef, floatingStyles } =
111+
useFloating<HTMLLabelElement>({
112+
shouldUpdate: isOpen,
113+
});
113114

114115
const scrollRef = useInfinityScroll(onScrollEnd);
115116

116-
const inputProps = getInputProps({
117-
id,
118-
ref,
119-
});
117+
// Downshift requires ref for input focus management - it assigns, not reads, during render
118+
// eslint-disable-next-line react-hooks/refs
119+
const inputProps = getInputProps({ id, ref });
120120

121121
return (
122122
<Box display="flex" flexDirection="column">
123123
<ComboboxWrapper
124124
id={id}
125-
ref={refs.reference}
125+
ref={setReferenceRef}
126126
typed={typed}
127127
active={active}
128128
disabled={disabled}
@@ -167,7 +167,7 @@ const DynamicComboboxInner = <T extends Option>(
167167
<List
168168
as="ul"
169169
className={listStyle}
170-
{...getMenuProps({ ref: refs.floating })}
170+
{...getMenuProps({ ref: setFloatingRef })}
171171
>
172172
{isOpen &&
173173
itemsToSelect?.map((item, index) => (

src/components/Combobox/Static/Combobox.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,20 @@ const ComboboxInner = <T extends Option, V extends Option | string>(
9898
onBlur,
9999
});
100100

101-
const { refs, floatingStyles } = useFloating<HTMLLabelElement>({
102-
shouldUpdate: isOpen,
103-
});
101+
const { setReferenceRef, setFloatingRef, floatingStyles } =
102+
useFloating<HTMLLabelElement>({
103+
shouldUpdate: isOpen,
104+
});
104105

105-
const inputProps = getInputProps({
106-
id,
107-
ref,
108-
});
106+
// Downshift requires ref for input focus management - it assigns, not reads, during render
107+
// eslint-disable-next-line react-hooks/refs
108+
const inputProps = getInputProps({ id, ref });
109109

110110
return (
111111
<Box display="flex" flexDirection="column">
112112
<ComboboxWrapper
113113
id={id}
114-
ref={refs.reference}
114+
ref={setReferenceRef}
115115
typed={typed}
116116
active={active}
117117
disabled={disabled}
@@ -159,7 +159,7 @@ const ComboboxInner = <T extends Option, V extends Option | string>(
159159
<List
160160
as="ul"
161161
className={listStyle}
162-
{...getMenuProps({ ref: refs.floating })}
162+
{...getMenuProps({ ref: setFloatingRef })}
163163
>
164164
{isOpen &&
165165
itemsToSelect?.map((item, index) => (

src/components/Multiselect/Dynamic/DynamicMultiselect.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,21 @@ const DynamicMultiselectInner = <T extends Option>(
114114
onBlur,
115115
});
116116

117-
const { refs, floatingStyles } = useFloating<HTMLLabelElement>({
118-
shouldUpdate: isOpen,
119-
});
117+
const { setReferenceRef, setFloatingRef, floatingStyles } =
118+
useFloating<HTMLLabelElement>({
119+
shouldUpdate: isOpen,
120+
});
120121

121122
const scrollRef = useInfinityScroll(onScrollEnd);
122123

123-
const inputProps = getInputProps({
124-
id,
125-
ref,
126-
value: inputValue,
127-
});
124+
// Downshift requires ref for input focus management - it assigns, not reads, during render
125+
// eslint-disable-next-line react-hooks/refs
126+
const inputProps = getInputProps({ id, ref, value: inputValue });
128127

129128
return (
130129
<Box display="flex" flexDirection="column">
131130
<MultiselectWrapper
132-
ref={refs.reference}
131+
ref={setReferenceRef}
133132
id={id}
134133
typed={typed}
135134
active={active}
@@ -211,7 +210,7 @@ const DynamicMultiselectInner = <T extends Option>(
211210
<List
212211
as="ul"
213212
className={listStyle}
214-
{...getMenuProps({ ref: refs.floating })}
213+
{...getMenuProps({ ref: setFloatingRef })}
215214
>
216215
{isOpen &&
217216
itemsToSelect?.map((item, index) => (

src/components/Multiselect/Static/Multiselect.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,20 @@ const MultiselectInner = <T extends Option, V extends Option | string>(
105105
onBlur,
106106
});
107107

108-
const { refs, floatingStyles } = useFloating<HTMLLabelElement>({
109-
shouldUpdate: isOpen,
110-
});
108+
const { setReferenceRef, setFloatingRef, floatingStyles } =
109+
useFloating<HTMLLabelElement>({
110+
shouldUpdate: isOpen,
111+
});
111112

112-
const inputProps = getInputProps({
113-
id,
114-
ref,
115-
value: inputValue,
116-
});
113+
// Downshift requires ref for input focus management - it assigns, not reads, during render
114+
// eslint-disable-next-line react-hooks/refs
115+
const inputProps = getInputProps({ id, ref, value: inputValue });
117116

118117
return (
119118
<Box display="flex" flexDirection="column">
120119
<MultiselectWrapper
121120
id={id}
122-
ref={refs.reference}
121+
ref={setReferenceRef}
123122
typed={typed}
124123
active={active}
125124
disabled={disabled}
@@ -194,7 +193,7 @@ const MultiselectInner = <T extends Option, V extends Option | string>(
194193
<List
195194
as="ul"
196195
className={listStyle}
197-
{...getMenuProps({ ref: refs.floating })}
196+
{...getMenuProps({ ref: setFloatingRef })}
198197
>
199198
{isOpen &&
200199
itemsToSelect?.map((item, index) => (

src/components/Select/Select.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,10 @@ const SelectInner = <T extends Option, V extends Option | string>(
114114
onBlur,
115115
});
116116

117-
const { refs, floatingStyles } = useFloating<HTMLLabelElement>({
118-
shouldUpdate: isOpen,
119-
});
117+
const { setReferenceRef, setFloatingRef, floatingStyles } =
118+
useFloating<HTMLLabelElement>({
119+
shouldUpdate: isOpen,
120+
});
120121

121122
const labelColor = useMemo((): TextProps["color"] => {
122123
if (error) {
@@ -143,7 +144,7 @@ const SelectInner = <T extends Option, V extends Option | string>(
143144
className={className}
144145
getLabelProps={getLabelProps}
145146
getToggleButtonProps={() =>
146-
getToggleButtonProps({ ref: refs.reference })
147+
getToggleButtonProps({ ref: setReferenceRef })
147148
}
148149
>
149150
<Box height={getBoxHeight(size)} {...props} ref={ref} display="flex">
@@ -176,7 +177,7 @@ const SelectInner = <T extends Option, V extends Option | string>(
176177
<List
177178
as="ul"
178179
className={listStyle}
179-
{...getMenuProps({ ref: refs.floating })}
180+
{...getMenuProps({ ref: setFloatingRef })}
180181
>
181182
{isOpen &&
182183
options?.map((item, index) => (

src/components/Textarea/Textarea.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
6262
typed,
6363
} = useStateEvents(value, onChange);
6464
const textAreaRef = useRef<HTMLTextAreaElement>(null);
65-
useAutoHeightTextarea(textAreaRef.current, value, rows, maxRows);
65+
useAutoHeightTextarea(textAreaRef, value, rows, maxRows);
6666
useImperativeHandle(ref, () => textAreaRef.current!);
6767

6868
return (

src/hooks/useAutoHeightTextarea.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1-
import { useEffect } from "react";
1+
import { RefObject, useEffect } from "react";
2+
23
import { TextareaValue } from "../components/Textarea/TextareaWrapper";
34

45
// Updates the height of a <textarea> when the value changes.
56
export const useAutoHeightTextarea = (
6-
textAreaRef: HTMLTextAreaElement | null,
7+
textAreaRef: RefObject<HTMLTextAreaElement>,
78
value: TextareaValue,
89
rows: number,
910
maxRows: number
1011
) => {
11-
const intialHeight = getHeight(textAreaRef, rows);
12-
const maxRowsHeight = getHeight(textAreaRef, maxRows);
13-
1412
useEffect(() => {
15-
if (textAreaRef) {
16-
// Restart the height at 0px to ensure that the scroll height is correct.
17-
textAreaRef.style.height = "0px";
13+
const element = textAreaRef.current;
14+
if (!element) return;
15+
16+
const initialHeight = getHeight(element, rows);
17+
const maxRowsHeight = getHeight(element, maxRows);
18+
19+
// Restart the height at 0px to ensure that the scroll height is correct.
20+
element.style.height = "0px";
1821

19-
// Take the max of the initial height and the scroll height for case where rows is greater than one.
20-
const initMaxHeight = Math.max(intialHeight, textAreaRef.scrollHeight);
21-
// Take the scroll height but limit it to the max rows height.
22-
const scrollHeight = Math.min(initMaxHeight, maxRowsHeight);
22+
// Take the max of the initial height and the scroll height for case where rows is greater than one.
23+
const initMaxHeight = Math.max(initialHeight, element.scrollHeight);
24+
// Take the scroll height but limit it to the max rows height.
25+
const scrollHeight = Math.min(initMaxHeight, maxRowsHeight);
2326

24-
textAreaRef.style.height = `${scrollHeight}px`;
25-
}
26-
}, [intialHeight, maxRowsHeight, textAreaRef, value]);
27+
element.style.height = `${scrollHeight}px`;
28+
}, [textAreaRef, value, rows, maxRows]);
2729
};
2830

29-
function getHeight(textAreaRef: HTMLTextAreaElement | null, rows: number) {
30-
if (textAreaRef) {
31-
return parseFloat(getComputedStyle(textAreaRef).lineHeight) * rows;
32-
}
33-
return 0;
31+
function getHeight(element: HTMLTextAreaElement, rows: number) {
32+
return parseFloat(getComputedStyle(element).lineHeight) * rows;
3433
}

src/hooks/useFloating.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export const useFloating = <T extends ReferenceType>({
1818
shouldUpdate,
1919
}: UseFloatingProps): {
2020
floatingStyles: UseFloatingReturn<T>["floatingStyles"] & { zIndex: number };
21-
refs: UseFloatingReturn<T>["refs"];
21+
setReferenceRef: (node: T | null) => void;
22+
setFloatingRef: (node: HTMLElement | null) => void;
2223
} => {
2324
const { floatingStyles, refs, update } = useFloatingHook<T>({
2425
strategy: "fixed",
@@ -37,14 +38,19 @@ export const useFloating = <T extends ReferenceType>({
3738
],
3839
});
3940

41+
// Use floating-ui's provided callback refs
42+
const setReferenceRef = refs.setReference;
43+
const setFloatingRef = refs.setFloating;
44+
4045
useLayoutEffect(() => {
4146
if (shouldUpdate && refs.reference.current && refs.floating.current) {
4247
return autoUpdate(refs.reference.current, refs.floating.current, update);
4348
}
4449
}, [shouldUpdate, refs, update]);
4550

4651
return {
47-
refs,
52+
setReferenceRef,
53+
setFloatingRef,
4854
floatingStyles: {
4955
...floatingStyles,
5056
zIndex: zIndexValue,

src/hooks/useInfinityScroll.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { useEffect, useRef, useState } from "react";
1+
import { useEffect, useRef } from "react";
22

33
export const useInfinityScroll = (onScrollEnd?: () => void) => {
44
const observerTarget = useRef<HTMLElement | null>(null);
5-
const [isIntersecting, setIsIntersecting] = useState(false);
65
const observer = useRef<IntersectionObserver | null>(null);
6+
const callbackRef = useRef(onScrollEnd);
77

8+
// Keep callback ref updated to avoid stale closures
89
useEffect(() => {
9-
if (isIntersecting) {
10-
onScrollEnd?.();
11-
setIsIntersecting(false);
12-
}
13-
}, [isIntersecting, onScrollEnd]);
10+
callbackRef.current = onScrollEnd;
11+
}, [onScrollEnd]);
1412

1513
useEffect(() => {
1614
const target = observerTarget.current;
@@ -26,7 +24,8 @@ export const useInfinityScroll = (onScrollEnd?: () => void) => {
2624
observer.current = new IntersectionObserver(
2725
(entries) => {
2826
if (entries[0].isIntersecting) {
29-
setIsIntersecting(true);
27+
// Call callback directly instead of using state as trigger
28+
callbackRef.current?.();
3029
}
3130
},
3231
{ threshold: 0 }

0 commit comments

Comments
 (0)