Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Emokores's avatar

Checkbox not working - Inertia with ReactJS

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.

0 likes
31 replies
Sinnbeck's avatar

Didn't we already get this working like several months ago?

Show that CheckBox component

Emokores's avatar

@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

Sinnbeck's avatar

@Emokores Me neither. But to help please show the code for the Checkox (I assume that is a typo?)

Sinnbeck's avatar

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}>
Emokores's avatar

@Sinnbeck okay. I didn't know what. Do you think that's what is causing the error?

Emokores's avatar

@Sinnbeck yes it is a typo. Otherwise, the code is the same as I used previously. However, I'm going to post the code

Sinnbeck's avatar

@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 :)

Emokores's avatar

@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>

Sinnbeck's avatar

@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's avatar

@Emokores Yes. Thats why I made a comment that you would need to use actual logic instead of false :)

Emokores's avatar

@Sinnbeck It seems I'm stuck on which logic to write on the checked prop. I'm still getting errors

Sinnbeck's avatar

@Emokores Same error? And the error happens the second you check one of the checkboxes?

Emokores's avatar

@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

Emokores's avatar

@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's avatar

@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

Sinnbeck's avatar

@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)
Emokores's avatar

@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's avatar

@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>
    );
}


Sinnbeck's avatar

@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,
        }));
    };
Sinnbeck's avatar

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.
Emokores's avatar

@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

Emokores's avatar

@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)
     );
};

Emokores's avatar

@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.

Emokores's avatar

@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.