Didn't we already get this working like several months ago?
Show that CheckBox component
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
I have a form in a modal that creates a user with roles. When I try to check the roles and output the data in the console, I get the error: Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the component's lifetime.
const {roles, users} = usePage().props, {data} = users
const handleChecked = (e) => {
const {value, checked} = e.target
checked ? setValues("roles", [...values.roles, value]) :
setValues("roles", roles.filter((e) => e !== value))
}
const [values, setValues] = useState({
//fields for the form
roles: []
})
<Table>
{data.map((user, I) => (
<tr key={i}>
{/* table data */}
</tr>
))}
</Table>
<ModalForm>
{/*form fields... */}
{roles.map((role, i) => (
<label key={i}>
<Checkox onChange={handleChecked} value={role.id} />
</label>
))}
</ModalForm>
And when I submit the form data, I get the result undefined in the console. I used this method in a previous project and it worked. For this case, I have a table (loads relationship roles from the backend) and a modal (with a create form) but in the previous project, I had a separate Create page to handle user input. So, I don't know whether it is a data conflict between the relationship roles of the table and the usePage() data roles.
This is in the index() method:
$search = $request->query('search');
return inertia('Admin/Users/Index', [
'users' => User::query()->when($search, fn($query) =>
$query->where('name', 'LIKE', "%{$search}%")
->orWhere('email', 'LIKE', "%{$search}%")
->orWhere('mobile_number', 'LIKE', "%{$search}%")
->orWhere('mobile_number_2', 'LIKE', "%{$search}%"))
->with('roles')->orderByDesc('created_at')->paginate(10)
->onEachSide(0)->withQueryString(),
'roles' => Role::get(),
]);
I've been stuck with this for hours today, and couldn't seem to get it done, even with a lot of googling.
Didn't we already get this working like several months ago?
Show that CheckBox component
@Sinnbeck yes we did. I did a different page structure this time. I have a modal with a create form and a table on the same page instead of a separate create page. I don't know why I'm getting the errors
@Emokores Me neither. But to help please show the code for the Checkox (I assume that is a typo?)
Btw. Never use index as key!. If you ever change the order of the data, react wont have any way of knowing which is which. Use id instead, as that does not change.
<label key={role.id}>
@Sinnbeck okay. I didn't know what. Do you think that's what is causing the error?
@Sinnbeck yes it is a typo. Otherwise, the code is the same as I used previously. However, I'm going to post the code
@Emokores No pretty sure it isnt. The error comes when from a misconfigured checkbox. My bet is that you havent given the checkbox a checked state, but are trying to set on at runtime. So show the component please :)
@Sinnbeck This is the component:
export default function Checkbox({
name,
value = "",
handleChange,
checked,
id,
}) {
return (
<input
type="checkbox"
name={name}
id={id}
checked={checked}
value={value}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
onChange={handleChange}
/>
);
}
And this is the component in the form:
<div className="col-span-6 space-y-3">
<span className="block font-medium text-sm text-indigo-700 mb-1">
Assign user role:{" "}
</span>
{roles.map((role, i) => {
return (
<label className="flex items-center" key={i}>
<Checkbox
name="roles[]"
id={`role${role.id}`}
value={role.id}
handleChange={handleChecked}
/>
<span className="ml-2 text-sm text-indigo-600">
{role.role}
</span>
</label>
);
})}
</div>
@Emokores You are not setting whether it should be checked
<Checkbox
name="roles[]"
id={`role${role.id}`}
value={role.id}
handleChange={handleChecked}
checked={false} //replace with actual logic for whether they are checked.
/>
@Sinnbeck okay. Previously (6months ago), I never did that and it was working.
@Emokores Ok. But its working now or?
@Sinnbeck won't it fixate the values?
@Emokores Yes. Thats why I made a comment that you would need to use actual logic instead of false :)
@Sinnbeck I'm going to try it when I reach my desk
@Sinnbeck It seems I'm stuck on which logic to write on the checked prop. I'm still getting errors
@Emokores Same error? And the error happens the second you check one of the checkboxes?
@Sinnbeck I also realized that when I check and uncheck the checkboxes, I get the error: TypeError: values.roles is not iterable. I don't know what's going on
@Sinnbeck Not the same error. I get the cannot get the value of undefined error. I think it's happening because I'm stuck on which logic to write.
@Emokores Show what you have now :)
@Sinnbeck This is what i have:
<Checkbox
name="roles[]"
id={`role${role.id}`}
value={role.id}
handleChange={handleChecked}
checked={values.roles.includes(role.id)
/>
And this is the error I get TypeError: Cannot read properties of undefined (reading 'includes') whenever I check any of the boxes
@Emokores Ok. A few things
Not sure what the point of this is?
id={`role${role.id}`}
Maybe just this?
id={role.id}
secondly, ensure that values has an array named roles. Add this before return
console.log(values)
@Sinnbeck It has an array named roles.
const [values, setValues] = useState({
//fields for the form
roles: []
})
And when I try to console.log() the data, I get undefined
@Emokores Can you show the complete file?
@Sinnbeck This is the Index.jsx file:
import { Head, usePage, Link } from "@inertiajs/inertia-react";
import { useState } from "react";
import { Inertia } from "@inertiajs/inertia";
import Authenticated from "@/Layouts/Authenticated";
import Input from "@/Components/Input";
import ModalForm from "@/Components/ModalForm";
import Button from "@/Components/Button";
import Label from "@/Components/Label";
import Select from "@/Components/Select";
import Checkbox from "@/Components/Checkbox";
import Table from "@/Components/Table";
import TabGroup from "@/Components/TabGroup";
import ErrorMessage from "@/Components/ErrorMessage";
import Paginator from "@/Components/Paginator";
import PaginationIndicator from "@/Components/PaginationIndicator";
import { format, parseISO } from "date-fns";
import { RefreshIcon } from "@heroicons/react/outline";
import { Tab } from "@headlessui/react";
import Avatar from "react-avatar";
export default function Index(props) {
const { users, roles, processing, errors } = usePage().props,
{ data } = users;
const [categories] = useState({
Users: data,
Payroll: [],
});
const genderOptions = [
{ id: "F", label: "Female" },
{ id: "M", label: "Male" },
];
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState("");
const [values, setValues] = useState({
name: "",
email: "",
gender: "",
mobile_number: "",
mobile_number_2: "",
roles: [],
}),
[query, setQuery] = useState("");
const handleChecked = (e) => {
const { checked, value } = e.target;
checked
? setValues("roles", [...values.roles, value])
: setValues(
"roles",
roles.filter((e) => e !== value)
);
};
const submit = (e) => {
e.preventDefault();
// values.gender = selected.id?.toString() || "";
// Inertia.post(route("admin.users.store"), values);
console.log(values);
setOpen(false);
// initialize the controls
setValues({
name: "",
email: "",
gender: "",
mobile_number: "",
mobile_number_2: "",
roles: [],
});
setSelected("");
};
const handleChange = (e) => {
setValues((values) => ({
...values,
[e.target.name]: e.target.value,
}));
};
const search = (e) => {
//* Prevent default action if you're using a form
// e.preventDefault();
Inertia.get(
route(route().current()),
{ search: query },
{
preserveState: true,
replace: true,
}
);
};
return (
<Authenticated auth={props.auth} errors={props.errors} header="Users">
<Head title="Team" />
<div className="flex items-center justify-center">
<ErrorMessage />
</div>
<section>
<TabGroup categories={categories}>
<Tab.Panel>
<div className="flex items-center justify-between mb-10">
<div className="text-gray-500 text-base">
A list of all users in your system including
name, email, phone number and their role.
</div>
<div className="flex items-center justify-center sm:justify-start">
<Button
className="w-[150px] mt-5 sm:mt-0 flex items-center justify-center"
onClick={() => {
setOpen(true);
}}
>
Add user
</Button>
</div>
</div>
<div className="mb-5 lg:flex lg:flex-1 lg:items-center lg:justify-between">
<div className="flex items-center justify-between">
<Input
type="search"
name="search"
value={query}
id="search"
autoComplete="search"
className="mt-1 block w-full shadow-inner sm:text-sm"
placeholder={`name, email, phone number...`}
handleChange={(e) =>
setQuery(e.target.value)
}
/>
<div className="flex items-center">
<Button
processing={processing}
className="ml-3"
onClick={search}
>
Search
</Button>
<Link href={route(route().current())}>
<RefreshIcon
className="h-6 w-6 ml-2 text-indigo-600"
aria-hidden="true"
/>
</Link>
</div>
</div>
</div>
<div className="mb-5 text-right">
<PaginationIndicator
to={users.to}
from={users.from}
total={users.total}
/>
</div>
<Table>
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-600 uppercase"
>
Name
</th>
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-600 uppercase"
>
Phone
</th>
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-600 uppercase"
>
Role
</th>
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-600 uppercase"
>
Status
</th>
<th
scope="col"
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-600 uppercase"
>
Last Updated
</th>
<th
scope="col"
className="relative px-6 py-3"
>
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{data.map((user) => (
<tr
key={user.uuid}
className="hover:bg-gray-100 focus-within:bg-gray-100"
>
<td className="px-6 py-4 whitespace-nowrap w-[50%]">
<div className="flex items-center">
<div className="flex-shrink-0 w-10 h-10">
<Avatar
className="rounded-full shadow-md"
name={user.name}
size={40}
maxInitials={2}
textSizeRatio={2}
/>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-600">
{user.name}
</div>
<div className="text-sm text-gray-400">
{user.email}
</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
{user.mobile_number},{" "}
{user.mobile_number_2 ??
"other number not provided"}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{user.roles.map((role) => (
<div key={role.id}>
{role.id === 1 ? (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-green-800 bg-green-100 rounded-full">
{role.role}
</span>
) : role.id === 2 ? (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-indigo-800 bg-indigo-100 rounded-full">
{role.role}
</span>
) : role.id === 3 ? (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-yellow-800 bg-yellow-100 rounded-full">
{role.role}
</span>
) : role.id === 4 ? (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-gray-800 bg-gray-100 rounded-full">
{role.role}
</span>
) : role.id === 5 ? (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-blue-800 bg-blue-100 rounded-full">
{role.role}
</span>
) : (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-purple-800 bg-purple-100 rounded-full">
{role.role}
</span>
)}
</div>
))}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{user.is_active === 1 ? (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-green-800 bg-green-100 rounded-full">
Active
</span>
) : (
<span className="inline-flex px-2 text-xs font-semibold leading-5 text-yellow-800 bg-yellow-100 rounded-full">
Inactive
</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-400">
{format(
parseISO(
user.updated_at
.toString()
.substring(0, 10)
),
"MMMM d, yyyy"
)}
</td>
<td className="px-6 py-4 text-sm font-medium text-right whitespace-nowrap">
<Link
href={route(
"admin.users.details",
user.uuid
)}
className="text-indigo-600 mr-3 hover:text-indigo-900 font-medium"
>
Details
</Link>
</td>
</tr>
))}
</tbody>
</Table>
<Paginator
links={users.links}
previous={users.prev_page_url}
next={users.next_page_url}
/>
</Tab.Panel>
</TabGroup>
</section>
{/* New user modal */}
<ModalForm
onSubmit={submit}
label="create user"
open={open}
setOpen={setOpen}
heading="Create New User"
>
<div className="grid grid-cols-6 gap-6">
<div className="col-span-6">
<Label forInput="name" value="Name" />
<Input
type="text"
name="name"
value={values.name}
autoComplete="name"
className="mt-1 block w-full shadow-sm sm:text-sm"
handleChange={handleChange}
/>
{errors.name && (
<span className="text-xs text-red-600">
{errors.name}
</span>
)}
</div>
<div className="col-span-6 sm:col-span-4">
<Label forInput="gender" value="Gender" />
<Select
name="gender"
options={genderOptions}
onChange={setSelected}
placeholder={`Select gender...`}
value={selected}
label="label"
valueKey="id"
className="mt-1 block w-full shadow-inner"
/>
{errors.gender && (
<span className="text-red-600 text-xs">
{errors.gender}
</span>
)}
</div>
<div className="col-span-6 sm:col-span-4">
<Label forInput="email" value="Email address" />
<Input
type="email"
name="email"
value={values.email}
autoComplete="email"
handleChange={handleChange}
className="mt-1 block w-full shadow-sm sm:text-sm"
/>
{errors.email && (
<span className="text-xs text-red-600">
{errors.email}
</span>
)}
</div>
<div className="col-span-6 sm:col-span-3">
<Label forInput="mobile_number" value="Mobile number" />
<Input
type="tel"
name="mobile_number"
value={values.mobile_number}
autoComplete="mobile_number"
handleChange={handleChange}
className="mt-1 block w-full shadow-sm sm:text-sm"
/>
{errors.mobile_number && (
<span className="text-xs text-red-600">
{errors.mobile_number}
</span>
)}
</div>
<div className="col-span-6 sm:col-span-3">
<Label
forInput="mobile_number_2"
value="Mobile number (Optional)"
/>
<Input
type="tel"
name="mobile_number_2"
value={values.mobile_number_2}
autoComplete="mobile_number_2"
handleChange={handleChange}
className="mt-1 block w-full shadow-sm sm:text-sm"
/>
</div>
<div className="col-span-6 space-y-3">
<span className="block font-medium text-sm text-indigo-700 mb-1">
Assign user role:{" "}
</span>
{roles.map((role, i) => {
return (
<label
className="flex items-center"
key={role.id}
>
<Checkbox
name="roles[]"
value={role.id}
handleChange={handleChecked}
/>
<span className="ml-2 text-sm text-indigo-600">
{role.role}
</span>
</label>
);
})}
</div>
</div>
</ModalForm>
</Authenticated>
);
}
@Emokores Ok. Try adding a console log in here. Perhaps it isnt getting that the name is "roles" and therefor breaks. Add this console log and click a checkbox
const handleChange = (e) => {
console.log(e.target.value)
setValues((values) => ({
...values,
[e.target.name]: e.target.value,
}));
};
Ah think I see the problem. You are replacing it with a single value, instead of appending to the array?
[e.target.name]: e.target.value, <-- will overwrite the array with a value.. You need a custom function to update the roles array.
@Sinnbeck I get the whole array:
email: "", gender: "", mobile_number: "", mobile_number_2: "", name: "", roles: Array(0) length: 0 [[Prototype]]: Array(0) roles[]: "3"
But instead of giving me the array of values for roles, I get the Array.length instead
@Sinnbeck How can I go about this?
@Sinnbeck I had this code, but it doesn't work:
const handleChecked = (e) => {
const { checked, value } = e.target;
checked
? setValues("roles", [...values.roles, value])
: setValues(
"roles",
roles.filter((e) => e !== value)
);
};
@Sinnbeck Okay I have still been trying but I now get an empty array [] in the console when I try to return values.roles in the console.
const handleChecked = (e) => {
const { checked, value } = e.target
const { roles } = values
checked
? setValues({ roles: [...roles, value] })
: setValues({ roles: roles.filter((e) => e !== value) })
};
const submit = (e) => {
e.preventDefault()
console.log(values.roles);
}
Then I had to change the onChange() prop to this:
<Checkbox onChange={() => handleChecked} />
But I still pass the checkbox data.
@Sinnbeck So now, I changed it to this:
const handleChecked = (e) => {
//* destructuring
const { checked, value } = e.target;
const { roles } = values;
checked
? setValues({ roles: [...roles, value] })
: setValues(roles.filter((e) => e !== value));
};
But whenever I try to submit the form data. I only see the checkbox values displayed.
Please or to participate in this conversation.