[bug]: Select Component Value Not Persisting After Page Reload
Describe the bug
The Select component is not maintaining its selected value after a page reload, even when the value is being saved to and retrieved from localStorage.
Affected component/components
Select
How to reproduce
1- Create a new component using the Select component from shadcn/ui. 2- Implement localStorage to save and retrieve the selected value. 3- Select a value from the dropdown. 4- Reload the page.
Expected Behavior:
The Select component should display the previously selected value after page reload.
Actual Behavior:
The Select component resets to its default state (showing the placeholder) after page reload, even though the correct value is being retrieved from localStorage.
Code Example:
"use client";
import { useEffect, useState } from "react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const TestComp = () => {
const [mode, setMode] = useState<string | undefined>(undefined);
useEffect(() => {
const keyMode = localStorage.getItem("keyMode");
if (keyMode) {
setMode(keyMode);
}
}, []);
useEffect(() => {
if (mode !== undefined) {
localStorage.setItem("keyMode", mode);
}
}, [mode]);
return (
<div>
<form>
<Select value={mode} onValueChange={(value) => setMode(value)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="fast">Fast ⚡</SelectItem>
<SelectItem value="quality">Quality 💎</SelectItem>
</SelectContent>
</Select>
<div>Mode: {mode || "Not selected"}</div>
</div>
<Button type="submit">Submit</Button>
</form>
);
};
export default TestComp;
Codesandbox/StackBlitz link
No response
Logs
No response
System Info
Next.js version: 14.2.3
React version: 18
Browser: Chrome
Before submitting
- [X] I've made research efforts and searched the documentation
- [X] I've searched for existing issues
Try to set the initial state to an empty string instead of undefined
Update: I believe I've identified the source of the problem. It appears to be specific to the shadcn/ui Select component when used within a form element. Here's what I've found:
When the Select component is wrapped in a form, it doesn't retrieve the value from localStorage on page reload. Removing the form wrapper allows the localStorage value to be correctly retrieved.
This behavior seems to be unique to the shadcn/ui implementation, as it doesn't occur with standard HTML select elements :
Not working :
<form>
<Select value={mode} onValueChange={(value) => setMode(value)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="fast">Fast ⚡</SelectItem>
<SelectItem value="quality">Quality 💎</SelectItem>
</SelectContent>
</Select>
</form>
Working (without form wrapper) :
<Select value={mode} onValueChange={(value) => setMode(value)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select mode" />
</SelectTrigger>
<SelectContent>
<SelectItem value="fast">Fast ⚡</SelectItem>
<SelectItem value="quality">Quality 💎</SelectItem>
</SelectContent>
</Select>
Working (basic select element in form) :
<form>
<select value={mode} onChange={(e) => setMode(e.target.value)}>
<option value="fast">Fast ⚡</option>
<option value="quality">Quality 💎</option>
</select>
</form>
Can confirm, in my setup the Select component also looses the value after a reload, but I get the value from a url query parameter instead. The component is also within a form, and the problem is not reproducible with an HTML select + option list elements.
edit: adding example as well (using Remix):
function MyComponent() {
const [searchParams] useSearchParams()
const options = [] /* some array of strings, also includes the default value from below */
const value = searchParams("my-value) || "default"
return (
<Form method="get">
<Select name="value" defaultValue={value}>
<SelectTrigger className="text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
{options.map((option) => (<SelectItem key={option} value={option}>{option}</SelectItem>))}
</SelectContent>
</Select>
</Form>
)
}
As mentioned, replacing it with an HTML select fixes the initial flickering of SelectValue.
Thank you guys for debugging the problem. I am facing the same issue right now.
I have a similar problem. When updating page, I get the data from sessionStorage. As well options are loaded from the server. When refreshing page, everything works correctly, but when switching from another page, the value in select is not substituted and, moreover, is transferred to sessionStorage.
{warehouses && (
<FormField
control={control}
name="warehouse"
render={({ field }) => {
console.info('warehouse control', field.value)
return (
<div>
<Select
onValueChange={field.onChange}
value={field.value?.toString()}
label="Склад отгрузки"
error={errors.warehouse?.message}
>
<SelectTrigger>
<SelectValue placeholder="Выберите склад" />
</SelectTrigger>
<SelectContent>
{warehouses?.warehouses.map(item => (
<SelectItem
key={item.id}
value={item.id.toString()}
>
{item.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)
}}
/>
)}
For me helps small crutch
useEffect(() => {
const val = getValues('warehouse')
setTimeout(() => {
setValue('warehouse', val)
}, 0)
}, [])
Another solution I found is to add a key to the select-field and increase/change it whenever you need programmatic changes done:
const [mode, setMode] = useState<string | undefined>(undefined);
const [formKey, setFormKey] = useState<number>(0);
useEffect(() => {
if (mode !== undefined) {
localStorage.setItem("keyMode", mode);
setFormKey(k => k +1)
}
}, [mode]);
//and in the form
<Select key={`modeselect${formKey}`}>...</Select>