jimofthestoneage's avatar

`patch` results in full page refresh when request validation fails. How does Inertia expect one to use `patch` in nested React components with partial reload?

I have a component that enables the user to modify a record in the database. Regardless of if I use { only: ['account'], preserveUrl: true, preserveState: true, preserveScroll: true }—the page fully refreshes when a validation exception is thrown which results in my component failing to inherit the errors bag.

Does Inertia expect me to prefer Axios for these component-level patch calls?

1 like
13 replies
jimofthestoneage's avatar

Edit Correction to this post: A full page refresh is occurring on all requests—even successful ones.

I was under the impression it was only happening on validation failure.

Happy to.

I excluded it in my opening post because the Request is throwing a validation error before the controller method is executed.

// Controller
    public function update(UpdateFinancialAccountsRequest $request, FinancialAccount $account)
    {
        $account->update($request->validated());

        return redirect()->intended(url()->previous(), 303);
    }
// Request rules
 public function rules(Request $request): array
    {
        return [
            'name' => [
                'min:1',
                'max:255',
                Rule::unique('financial_accounts', 'name')
                    ->where('user_id', $request->user()->id),
            ],
            'current_balance' => [
                'numeric',
                'nullable',
                Rule::when(
                    $request->input('type') === FinancialAccountType::Cash->value,
                    ['gte:0']
                ),
            ],
            'type' => [Rule::in(FinancialAccountType::supported())],
        ];
    }

In the scenario outlined in my opening post, I am intentionally submitting a value for name that breaks the unique rule. It's when the validation error is thrown that the full page refresh is triggered.

jimofthestoneage's avatar

Sorry, I was incorrect. A full page refresh is occurring on all requests—even successful ones.

I was under the impression it was only happening on validation failure.

jlrdw's avatar

How are you sending the request.

JussiMannisto's avatar

This is not what the intended() method is for:

return redirect()->intended(url()->previous(), 303);

That method is for redirecting the user to the URL that they were trying to access when an auth check failed and they were redirected to a login page. It's only appropriate to use it at the end of a successful login.

Here, you should just redirect back, or you can get incorrect behavior in some situations.

return back();

As for the full page reload, there could be many causes. Please show the front-end request code and the form page controller method.

jimofthestoneage's avatar

I'm using patch from useForm to send the request.

Regarding the controller return, back(303) is what I had implemented originally.

I'll provide the full code when I get home.

jimofthestoneage's avatar

Here's the code I promised. The only thing I can imagine is that this is somehow due to the fact that I am calling the patch from a subcomponent. I'm at a loss for what is triggering the page reload. It occurs regardless of if the request is abandoned due to a validation error or if the request completes successfully.

// Controller
    public function update(UpdateFinancialAccountsRequest $request, FinancialAccount $account)
    {
        $account->update($request->validated());

        return back();
    }
// Page component
export default function Index({ financialAccounts }: { financialAccounts: FinancialAccountModel[] }) {
    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Dashboard" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                <div className="grid auto-rows-min gap-4 md:grid-cols-3">
                    {financialAccounts?.map((acc) => 
                            <FinancialAccountSummary key={acc.id} account={acc} patchOptions={{ only: ['financialAccounts'] }} />
                    )}
                </div>
...
// FinancialAccountSummary relevant content

    const { data, setData, processing, errors, reset, patch } = useForm(formDefaults);

<form
                    onSubmit={() => {
                        if (data.name.length === 0) return;

                        patch(route('financial-accounts.update', { account: account.id }), {
                            ...patchOptions,
                            onSuccess: () => {
                                console.log('success?');
                            },
                            onError: (err) => {
                                console.log('error?', err);
                            },
                        });
                    }}
                >

JussiMannisto's avatar

@jimofthestoneage Show the form page controller.

the page fully refreshes when a validation exception is thrown which results in my component failing to inherit the errors bag.

When you say the page fully refreshes, what exactly happens?

A validation exception is necessary for the form helper to work correctly. When Laravel throws a validation exception, it also passes validation errors in the response. When the form helper receives a 422 response, it reads the error messages from the response, hydrates the errors variable in the useForm hook, and sets processing to false. If there was no validation exception, you wouldn't get any error messages.

jimofthestoneage's avatar

@jussimannisto by "refresh" I mean it's as if the refresh button in the browser was clicked — the page completely reloads. Network history is lost in Dev tools, console log history is cleared, the place is fully reloadef. The interesting thing, however, is that errors is populated with the validation error.

If I can get to the root of why and when inertia chooses to truly reload the page vs when it chooses to populate the page props only, I imagine I can then get to the root of this issue.

Max100's avatar

@jimofthestoneage This probably isn’t the answer to your question, but the inertia preserveState option may be helpful. Also, remember to use <Link> tags rather than anchor tags on your pages, otherwise you’ll get a full page refresh.

JussiMannisto's avatar

@jimofthestoneage

Show the form page controller, i.e. the endpoint that returns the Inertia::render() call.

The reload could be caused by something on the backend, e.g. in the controller or a middleware.

jimofthestoneage's avatar

@JussiMannisto back() in the update() controller sends the user to:

    public function index(Request $request)
    {
        if ($request->user()->cannot('viewAny', FinancialAccount::class)) {
            abort(403);
        }
        return Inertia::render('financial-account/index', [
            'financialAccounts' => $request->user()->financialAccounts,
        ]);
    }
jimofthestoneage's avatar
Level 1

It always comes down to the basics, doesn't it? I was missing e.preventDefault() in my form onSubmit 😂

Please or to participate in this conversation.