Fix ModelSwitcher to derive selectedProviderId instead of setState in effect

Replace the problematic useEffect that set state synchronously with a
useMemo that derives the selected provider. This eliminates the double
render and satisfies the react-hooks/set-state-in-effect rule without
needing eslint-disable.

Changes:
- Renamed selectedProviderId state to manuallySelectedProviderId
- Use useMemo to derive selectedProviderId from:
  1. Manual selection (if user clicked a provider)
  2. Current model's provider (if a model is selected)
  3. First available provider (as fallback)
- No more setState in useEffect = no cascading renders
This commit is contained in:
Brendan Whiting
2026-01-17 11:01:55 -08:00
parent 9fa01bcff4
commit fa548bbb1e
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useState, useEffect, useMemo } from 'react'
import { ChevronDown, Check, AlertCircle, Key } from 'lucide-react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Button } from '@/components/ui/button'
@@ -53,7 +53,9 @@ interface ModelSwitcherProps {
export function ModelSwitcher({ threadId }: ModelSwitcherProps): React.JSX.Element {
const [open, setOpen] = useState(false)
const [selectedProviderId, setSelectedProviderId] = useState<ProviderId | null>(null)
const [manuallySelectedProviderId, setManuallySelectedProviderId] = useState<ProviderId | null>(
null
)
const [apiKeyDialogOpen, setApiKeyDialogOpen] = useState(false)
const [apiKeyProvider, setApiKeyProvider] = useState<Provider | null>(null)
@@ -69,20 +71,22 @@ export function ModelSwitcher({ threadId }: ModelSwitcherProps): React.JSX.Eleme
// Use fallback providers if none loaded
const displayProviders = providers.length > 0 ? providers : FALLBACK_PROVIDERS
// Set initial selected provider based on current model
useEffect(() => {
if (!selectedProviderId && currentModel) {
// Derive the selected provider from current model or manual selection
const selectedProviderId = useMemo(() => {
// If user manually selected a provider, use that
if (manuallySelectedProviderId) {
return manuallySelectedProviderId
}
// Otherwise, derive from current model
if (currentModel) {
const model = models.find((m) => m.id === currentModel)
if (model) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSelectedProviderId(model.provider)
return model.provider
}
}
// Default to first provider if none selected
if (!selectedProviderId && displayProviders.length > 0) {
setSelectedProviderId(displayProviders[0].id)
}
}, [currentModel, models, selectedProviderId, displayProviders])
// Default to first provider
return displayProviders[0]?.id || null
}, [manuallySelectedProviderId, currentModel, models, displayProviders])
const selectedModel = models.find((m) => m.id === currentModel)
const filteredModels = selectedProviderId
@@ -91,7 +95,7 @@ export function ModelSwitcher({ threadId }: ModelSwitcherProps): React.JSX.Eleme
const selectedProvider = displayProviders.find((p) => p.id === selectedProviderId)
function handleProviderClick(provider: Provider): void {
setSelectedProviderId(provider.id)
setManuallySelectedProviderId(provider.id)
}
function handleModelSelect(modelId: string): void {