Skip to content

Commit a78b52a

Browse files
committed
fix(apps): compare picker entries by modelName to handle accessor functions
useState auto-invokes function-typed initial values as lazy initializers, so passing a MODEL_REGISTRY accessor unwraps it into a plain config — breaking reference equality against the accessor stored in MODELS. Compare by modelName (falling back to === for picker users without one, e.g. VoiceConfig).
1 parent 64ba03c commit a78b52a

4 files changed

Lines changed: 52 additions & 8 deletions

File tree

apps/computer-vision/components/ModelPicker.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ type Props<T> = {
2424

2525
const DROPDOWN_MAX_HEIGHT = 200;
2626

27+
// MODEL_REGISTRY accessors are functions, so passing them to useState makes
28+
// React auto-invoke them as lazy initializers — state becomes the underlying
29+
// config object, breaking reference equality against the accessor in MODELS.
30+
// Match by modelName when both sides expose one, otherwise fall back to ===.
31+
function sameValue<T>(a: T, b: T): boolean {
32+
const am = (a as { modelName?: unknown })?.modelName;
33+
const bm = (b as { modelName?: unknown })?.modelName;
34+
if (typeof am === 'string' && typeof bm === 'string') return am === bm;
35+
return a === b;
36+
}
37+
2738
export function ModelPicker<T>({
2839
models,
2940
selectedModel,
@@ -36,7 +47,7 @@ export function ModelPicker<T>({
3647
const [expandUp, setExpandUp] = useState(false);
3748
const [dropdownTop, setDropdownTop] = useState(0);
3849
const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
39-
const selected = models.find((m) => m.value === selectedModel);
50+
const selected = models.find((m) => sameValue(m.value, selectedModel));
4051

4152
useEffect(() => {
4253
if (disabled) setOpen(false);
@@ -112,7 +123,7 @@ export function ModelPicker<T>({
112123
showsVerticalScrollIndicator={true}
113124
>
114125
{models.map((item) => {
115-
const isSelected = item.value === selectedModel;
126+
const isSelected = sameValue(item.value, selectedModel);
116127
return (
117128
<TouchableOpacity
118129
key={item.label}

apps/llm/components/ModelPicker.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ type Props<T> = {
2424

2525
const DROPDOWN_MAX_HEIGHT = 200;
2626

27+
// MODEL_REGISTRY accessors are functions, so passing them to useState makes
28+
// React auto-invoke them as lazy initializers — state becomes the underlying
29+
// config object, breaking reference equality against the accessor in MODELS.
30+
// Match by modelName when both sides expose one, otherwise fall back to ===.
31+
function sameValue<T>(a: T, b: T): boolean {
32+
const am = (a as { modelName?: unknown })?.modelName;
33+
const bm = (b as { modelName?: unknown })?.modelName;
34+
if (typeof am === 'string' && typeof bm === 'string') return am === bm;
35+
return a === b;
36+
}
37+
2738
export function ModelPicker<T>({
2839
models,
2940
selectedModel,
@@ -36,7 +47,7 @@ export function ModelPicker<T>({
3647
const [expandUp, setExpandUp] = useState(false);
3748
const [dropdownTop, setDropdownTop] = useState(0);
3849
const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
39-
const selected = models.find((m) => m.value === selectedModel);
50+
const selected = models.find((m) => sameValue(m.value, selectedModel));
4051

4152
useEffect(() => {
4253
if (disabled) setOpen(false);
@@ -112,7 +123,7 @@ export function ModelPicker<T>({
112123
showsVerticalScrollIndicator={true}
113124
>
114125
{models.map((item) => {
115-
const isSelected = item.value === selectedModel;
126+
const isSelected = sameValue(item.value, selectedModel);
116127
return (
117128
<TouchableOpacity
118129
key={item.label}

apps/speech/components/ModelPicker.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ type Props<T> = {
2323

2424
const DROPDOWN_MAX_HEIGHT = 200;
2525

26+
// MODEL_REGISTRY accessors are functions, so passing them to useState makes
27+
// React auto-invoke them as lazy initializers — state becomes the underlying
28+
// config object, breaking reference equality against the accessor in MODELS.
29+
// Match by modelName when both sides expose one, otherwise fall back to ===.
30+
function sameValue<T>(a: T, b: T): boolean {
31+
const am = (a as { modelName?: unknown })?.modelName;
32+
const bm = (b as { modelName?: unknown })?.modelName;
33+
if (typeof am === 'string' && typeof bm === 'string') return am === bm;
34+
return a === b;
35+
}
36+
2637
export function ModelPicker<T>({
2738
models,
2839
selectedModel,
@@ -34,7 +45,7 @@ export function ModelPicker<T>({
3445
const [triggerHeight, setTriggerHeight] = useState(0);
3546
const [expandUp, setExpandUp] = useState(false);
3647
const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
37-
const selected = models.find((m) => m.value === selectedModel);
48+
const selected = models.find((m) => sameValue(m.value, selectedModel));
3849

3950
useEffect(() => {
4051
if (disabled) setOpen(false);
@@ -87,7 +98,7 @@ export function ModelPicker<T>({
8798
keyboardShouldPersistTaps="handled"
8899
>
89100
{models.map((item) => {
90-
const isSelected = item.value === selectedModel;
101+
const isSelected = sameValue(item.value, selectedModel);
91102
return (
92103
<TouchableOpacity
93104
key={item.label}

apps/text-embeddings/components/ModelPicker.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ type Props<T> = {
2424

2525
const DROPDOWN_MAX_HEIGHT = 200;
2626

27+
// MODEL_REGISTRY accessors are functions, so passing them to useState makes
28+
// React auto-invoke them as lazy initializers — state becomes the underlying
29+
// config object, breaking reference equality against the accessor in MODELS.
30+
// Match by modelName when both sides expose one, otherwise fall back to ===.
31+
function sameValue<T>(a: T, b: T): boolean {
32+
const am = (a as { modelName?: unknown })?.modelName;
33+
const bm = (b as { modelName?: unknown })?.modelName;
34+
if (typeof am === 'string' && typeof bm === 'string') return am === bm;
35+
return a === b;
36+
}
37+
2738
export function ModelPicker<T>({
2839
models,
2940
selectedModel,
@@ -36,7 +47,7 @@ export function ModelPicker<T>({
3647
const [expandUp, setExpandUp] = useState(false);
3748
const [dropdownTop, setDropdownTop] = useState(0);
3849
const triggerRef = useRef<React.ComponentRef<typeof TouchableOpacity>>(null);
39-
const selected = models.find((m) => m.value === selectedModel);
50+
const selected = models.find((m) => sameValue(m.value, selectedModel));
4051

4152
useEffect(() => {
4253
if (disabled) setOpen(false);
@@ -112,7 +123,7 @@ export function ModelPicker<T>({
112123
showsVerticalScrollIndicator={true}
113124
>
114125
{models.map((item) => {
115-
const isSelected = item.value === selectedModel;
126+
const isSelected = sameValue(item.value, selectedModel);
116127
return (
117128
<TouchableOpacity
118129
key={item.label}

0 commit comments

Comments
 (0)