Working With Dynamic Data And Selects With React And Inertia Enter a brief summary for this post

Working with Dynamic Data and SELECTs with React and Inertia

It's a basic understanding required working with (CRUD) data is to be able to populate a SELECT drop down menu with data from the database. I've seen many tutorials and articles online but their examples in many cases are contrived.

When it comes to CRUD (Create, Read, Update and Delete) you need to account for:

  • the initial form with no options selected
  • existing data therefore with options selected

And thirdly, for a drop-down menu with multiple choice options. Which is why I say many examples are contrived because the articles never explore beyond the simplest examples.

Let's take a look at an easy example first (Create) and following on the Update.

React Inertia Form SELECTs

In this example there are two props. The data comes from a Laravel controller on the backend.

const { teams } = usePage().props; 
const { clients } = usePage().props;

There is one client to a Project but many teams to a Project. So, there is a need for a simple SELECT and a more complex SELECT menu. Each SELECT requires an event handler, as seen below.

    function handleTeamsSelect(e) {
        let values = Array.from(e.target.selectedOptions, option => option.value); 

        setData("teams", values);
    }

    function handleClientsSelect(e) {
        setData("client_id", e.target.selectedOptions[0].value);
    }

In my experience it's better (more enjoyable) to work with controlled inputs (backed up by state) than it is with uncontrolled inputs. Your opinion may differ of course.

The form in question is below, showing only the SELECTs we're concerned with, for brevity.

<div className="mt-4">
                                <InputLabel htmlFor="teams" value="Team Selection" />
                                <Select
                                    className="mt-1 block w-full"
                                    size={ 10 }
                                    multiple={ true }
                                    name="teams" 
                                    handleSelect={ handleTeamsSelect }
                                    rows={ teams }>Team Selection</Select>

                                <InputError message={ errors.teams } className="mt-2" />
                            </div>

                            <div className="mt-4">
                                <InputLabel htmlFor="client_id" value="Client Selection" />
                                <Select
                                    className="mt-1 block w-full"
                                    size={ 10 }
                                    multiple={ false }
                                    name="client_id" 
                                    handleSelect={ handleClientsSelect }
                                    rows={ clients }>Client Selection</Select>

                                <InputError message={ errors.client_id } className="mt-2" />
                            </div>

The <Select /> component comes next.

export default function Select({ className = '', size, multiple, name, handleSelect, value, rows, children, isFocused = false }) {

	useEffect(() => {
        if(isFocused) {
            input.current.focus();
        } 
    }, []);

	return (
	    <select 
			className={
                'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm ' + className}
	    	size={ size }
	    	multiple={ multiple }
            name={ name }
            onChange={ handleSelect }
	    	value={ value }>

			<option value="0">{ children }</option>
			{
				rows.map((row) => (
					<option key={ row.id } value={ row.id }>{ row.name }</option>
				))
			}
		</select>
	);
}

I use the same <Select /> component for both single and multiple-choice option menus. That pretty much covers most general SELECTs with React and Inertia. Recalling at the start there are two use cases. We've covered the first and now we cover the second (Update) use case.

The event handlers clearly remain the same albeit with additional props passed on from the backend.

    const { project } = usePage().props;
    const { clients } = usePage().props;
    const { teams } = usePage().props;
    const { defaults } = usePage().props;

    const { data, setData, post, processing, errors } = useForm({
        name: project.name,
        description: project.description,
        teams: defaults,
        client_id: project.client_id,
        id: project.id,
        _method: "put",
    });

I've included Inertia's useForm hook usage as well. Those props should be obvious what they are? Except may the defaults prop which happens to be the rows of data previously SELECTed.

There are slight differences in the form for an Update compared to a Create and they're shown below.

<div className="mt-4">
                                <InputLabel htmlFor="teams" value="Team Selection" />
                                <Select
                                    className="mt-1 block w-full"
                                    size={ 10 }
                                    multiple={ true }
                                    name="teams" 
                                    handleSelect={ handleTeamsSelect }
                                    value={ data.teams }
                                    rows={ teams }>Team Selection</Select>

                                <InputError message={ errors.teams } className="mt-2" />
                            </div>

                            <div className="mt-4">
                                <InputLabel htmlFor="client_id" value="Client Selection" />
                                <Select
                                    className="mt-1 block w-full"
                                    size={ 10 }
                                    multiple={ false }
                                    name="client_id" 
                                    handleSelect={ handleClientsSelect }
                                    value={ data.client_id }
                                    rows={ clients }>Client Selection</Select>

                                <InputError message={ errors.client_id } className="mt-2" />
                            </div>

With an Update you must set the values. The defaults are given over to the form data by (pun intended) default. That ensures when the SELECT in question is rendered the options are SELECTed.

Looking at the backend we can see what's happening a little more clearly.

$project = Project::where([
            'id' => $project->id, 
            'company_id' => auth()->guard('web')->user()->company_id,
        ])->firstOrFail();

        $teams = Team::where([
            'company_id' => auth()->guard('web')->user()->company_id,
        ])
        ->get();

        $clients = Client::where([
            'company_id' => auth()->guard('web')->user()->company_id,
        ])
        ->get();

        $project->load(['teams']);

        $defaults = $project->load(['teams'])->teams->pluck('id');

        return inertia('User/Project/Edit', [
            'project' => $project,
            'clients' => $clients,
            'teams' => $teams,
            'defaults' => $defaults,
            'breadcrumbs' => Breadcrumbs::generate('projects.edit', $project),
        ]);

For the default options selected we are only interested in the primary keys. That more or less concludes this post about using React with Inertia and dynamic SELECTs.

I've enjoyed working with Inertia thus far and intend to continue using it and can't wait until my next project I can consider Inertia 2 for it.