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

Emokores's avatar

How can I perform Live Search in Inertia with React and Laravel

I'm working on a Laravel project, and I'm using InertiaJS with ReactJS to render my views. I need to implement a live search on a datatable, but I have tried to implement the data filter on every keystroke and it has failed. I also need the search input to send data like localhost:8000/admin/users?search=some&value to the server. In my backend, I have implemented the query in the Index() method in my UsersController as below:

public function index()
{
        // return User::paginate(10);
        $query = User::query();

        if(request('search')) return $query->where('name', 'LIKE', '%'. request('search') . '%')->orWhere('email', 														 
        'LIKE', '%'. request('search') . '%');

        return Inertia::render('Admin/Users/Index', [
            'users' => $query->orderByDesc('created_at')->paginate(10),
            'roles' => Role::all(),
        ]);
        // ddd(User::get('name'));
}

This is what I implemented in the onChange event for the search input, and the state:

export default function Index(props) {
    const { users } = usePage().props;
    // Laravel's pagination data object = data
    const { data } = users,
        [query, setQuery] = useState({
            search: ""
    });

    useEffect(() => {
        Inertia.get(route(route().current()), query, {
            preserveState: true,
            replace: true,
        });
    }, [query]);

    return (
           <>
               <Input type="search" name="search" value={query} id="search" autoComplete="search" 																																																							 
    			className="mt-1 block w-full shadow-sm sm:text-sm" placeholder={`Search here...`} 																																																																																																														 
  				handleChange={(e) => setQuery(e.target.value)}/>
                <Table>
                      {... some data here ...}
                </Table>
           </>
      );
}

I have tried searching everywhere, but there seems to be limited information on this. I tried to understand this from the VueJS side of things and tried to implement it but failed. Any help on this will be appreciated. Thanks, big time!

0 likes
25 replies
Sinnbeck's avatar

I use the same stack and by a glance this code should work. Can you describing the issue a bit more than "and it has failed". Does the input loose focus? Does it keep rerendering because of useEffect()? Is it slow as you arent doing any debounce?

1 like
Emokores's avatar

@Sinnbeck The page misbehaves. It keeps reloading endlessly. And when I try to stop it, all my components disappear. And in the search input, it shows [object][object]. When I check in the network tab it keeps returning status 409 Conflict then it cancels the request. And the loop goes over and over. I think it keeps doing this because of the useEffect() watcher.

Sinnbeck's avatar

@Emokores ok. Let's take it bit by bit. You initialize it with an object, and then set it as a string. Start out with a string

[query, setQuery] = useState('');

And then set the data as an object

useEffect(() => {
        Inertia.get(route(route().current()), {search: query}, {
            preserveState: true,
            replace: true,
        });
    }, [query]); 
1 like
Emokores's avatar

Thanks so much for the hand. I clearly understand how it's working. However, when I do that, I get an error: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.

Sinnbeck's avatar

@Emokores hmm strange. I know why the error normally happens but here the input should have a value. Try using console.log to check that query always has a value. When I am at a computer, I will try out the code myself

Emokores's avatar

I actually get why the error was happening. I didn't put the quotes in the useState(). However, when I do that I don't see any response happening. when I provide input. And when I reload the page, this is what I get in the browser address bar: http://localhost:8000/admin/users?search=. But nothing happens when I try to filter.

Emokores's avatar

Lemme share the code with you. However, I'm still getting the status 409 Conflict.

Emokores's avatar

Here's the code:


import React, { useState, useEffect } from "react";
import Authenticated from "@/Layouts/Authenticated";
import { Inertia } from "@inertiajs/inertia";
import { Head, usePage, Link } from "@inertiajs/inertia-react";
import HeaderSection from "@/Layouts/HeaderSection";
import Table from "@/Components/Table";
import Avatar from "react-avatar";
import Input from "@/Components/Input";
import Button from "@/Components/Button";
import Pagination from "@/Components/Pagination";

export default function Index(props) {
    const { users } = usePage().props;
    // Laravel's pagination data object = data
    const { data } = users,
        [query, setQuery] = useState("");

    useEffect(() => {
        Inertia.get(
            route(route().current()),
            { search: query },
            {
                preserveState: true,
                replace: true,
            }
        );
    }, [query]);

    return (
        <>
            <Authenticated auth={props.auth}>
                <Head title="User Management" />

                <HeaderSection
                    linkLabel="create new user"
                    heading="user management"
                    current="user management"
                    link={route("admin.users.create")}
                ></HeaderSection>

                <section className="mt-5 p-5">
                    <div className="mb-5 lg:flex lg:flex-1 lg:items-center lg:justify-between">
                        <div className="flex items-center">
                            <Input
                                type="search"
                                name="search"
                                value={query}
                                id="search"
                                autoComplete="search"
                                className="mt-1 block w-full shadow-sm sm:text-sm"
                                placeholder={`Search here...`}
                                handleChange={(e) => setQuery(e.target.value)}
                            />
                            <Button className="ml-3">Reset</Button>
                        </div>
                        <Pagination
                            links={users.links}
                            from={users.from}
                            to={users.to}
                            total={users.total}
                            previous={users.prev_page_url}
                            next={users.next_page_url}
                        />
                    </div>
                    <Table>
                        <thead>
                            <tr>
                                <th
                                    scope="col"
                                    className="px-6 py-3 text-xs font-medium tracking-wider text-left text-indigo-600 uppercase"
                                >
                                    Name
                                </th>

                                <th
                                    scope="col"
                                    className="px-6 py-3 text-xs font-medium tracking-wider text-left text-indigo-600 uppercase"
                                >
                                    Status
                                </th>
                                <th scope="col" className="relative px-6 py-3">
                                    <span className="sr-only">Edit</span>
                                </th>
                            </tr>
                        </thead>
                        <tbody className="divide-y divide-indigo-200">
                            {data.map((user, index) => (
                                    <tr
                                        key={index}
                                        className="hover:bg-indigo-100 focus-within:bg-indigo-100"
                                    >
                                        <td className="px-6 py-4 whitespace-nowrap">
                                            <div className="flex items-center">
                                                <div className="flex-shrink-0 w-10 h-10">
                                                    <Avatar
                                                        className="rounded-full"
                                                        name={user.name}
                                                        size={40}
                                                        textSizeRatio={1.75}
                                                        color={Avatar.getRandomColor(
                                                            "sitebase",
                                                            [
                                                                "red",
                                                                "green",
                                                                "blue",
                                                                "orange",
                                                                "purple",
                                                            ]
                                                        )}
                                                    />
                                                </div>
                                                <div className="ml-4">
                                                    <div className="text-sm font-medium text-gray-900">
                                                        {user.name}
                                                    </div>
                                                    <div className="text-sm text-gray-500">
                                                        {user.email}
                                                    </div>
                                                </div>
                                            </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 text-sm font-medium text-right whitespace-nowrap">
                                            <button className="text-green-600 mr-3 outline-none focus:outline-none active:outline-none hover:text-green-900 font-medium">
                                                Show
                                            </button>
                                            <Link
                                                href="#"
                                                className="text-indigo-600 mr-3 hover:text-indigo-900"
                                            >
                                                Edit
                                            </Link>
                                            <button className="text-red-600 outline-none focus:outline-none active:outline-none hover:text-red-900 font-medium">
                                                Delete
                                            </button>
                                        </td>
                                    </tr>
                                ))}
                            {users.length === 0 && (
                                <tr>
                                    <td
                                        className="px-6 py-4 text-gray-400"
                                        colSpan="3"
                                    >
                                        No records found.
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </Table>
                    <Pagination
                        links={users.links}
                        from={users.from}
                        to={users.to}
                        total={users.total}
                        previous={users.prev_page_url}
                        next={users.next_page_url}
                    />
                </section>

                {/* Show modal */}
            </Authenticated>
        </>
    );
}


Sinnbeck's avatar

@Emokores if I recall correctly, then that is what inertia use to force a full page refresh. It is determined by the version method in the middleware

Emokores's avatar

@sinnbeck I haven't got you quite correctly. Can I show you what's in my middleware?

Emokores's avatar

@sinnbeck I already added the inertia middleware to my project during setup and everything works perfectly fine. It's only the filtering function for my datatable.

Emokores's avatar

@sinnbeck I have tried to create a button with an onClick() event that makes the Inertia.get() call. However, from Laravel I receive the following TypeError: Symfony\Component\HttpFoundation\Response::setContent(): Argument #1 ($content) must be of type ?string, Illuminate\Database\Eloquent\Builder given, called in /home/blackhawk/Workspace/hi5Media_f/vendor/laravel/framework/src/Illuminate/Http/Response.php

Sinnbeck's avatar

@Emokores sounds like you are passing a query builder instance to the reply. Did you change the controller method? Remove paginate() perhaps?

Emokores's avatar

@Sinnbeck No, I didn't. Everything is still as I showed it. Even before, I would hard-type the URL in the browser and it would return that same error.

Sinnbeck's avatar

@Emokores well somewhere you are adding a query to a response. I don't suppose it's on github as a public repo?

Emokores's avatar

@Sinnbeck Well, this is what I have in my backend:


public function index()
    {
        
        $query = User::query();

        if(request('search')) return $query->where('name', 'LIKE', '%'. request('search') . '%')->orWhere('email', 'LIKE', '%'. request('search') . '%');

        return Inertia::render('Admin/Users/Index', [
            'users' => $query->orderByDesc('created_at')->paginate(10)->withQueryString(),
            'roles' => Role::all(),
            'filters' => Request::only('search'),
        ]);
    }

Sinnbeck's avatar

@Emokores try test by removing one at a time to see which one is causing the error. If none of them causes it, the error is elsewhere

Emokores's avatar

@furqanDev I have watched the video but I can't seem to get it done. I have also followed similar videos done in React but nothing seems to be in my favour.

Emokores's avatar
Emokores
OP
Best Answer
Level 2

I solved this. I implemented two search methods: frontend with React, and backend with Laravel. I created a button that sends the request to the server to filter the data. I implemented the following code in my index method:

        $search = $request->query('search');

        return Inertia::render('Admin/Users/Index', [
            'users' => User::query()->when($search, fn ($query) =>
            $query->where('name', 'LIKE', "%{$search}%")->orWhere('email', 'LIKE', "%{$search}%")
        )->orderByDesc('created_at')->paginate(10)->withQueryString(),
            'roles' => Role::all(),
        ]);

This is what I have in the frontend. This method is an onClick event of a button:

const search = (e) => {

        Inertia.get(
            route(route().current()),
            { search: query },
            {
                preserveState: true,
                replace: true,
            }
        );
}

Nooxx's avatar

Simple solution is to remove the unecessary useEffect and useState, your state is in the query in that case:

// utils.ts
export function getParameterByName(name: string) {
    const uri = window.location.search
    const match = RegExp('[?&]' + name + '=([^&]*)').exec(uri)
    return match && decodeURIComponent(match[1].replace(/\+/g, ' '))
}

// in your page
const search = getParameterByName('q') || '';

const handleSearch = (query: string) => {
     Inertia.get('/', { q: query }, { preserveState: true });
}

...

<Input
	 name="query"
     onChange={(e) => handleSearch(e.target.value)}
     value={search}
/>

1 like

Please or to participate in this conversation.