Categories
Laravel

Dual-use Conditional Laravel Controller Methods for Inertia + Vue

Dual-use controllers in Laravel serve a dual purpose: they return/render an Inertia/Vue page when accessed normally and JSON data when requested via AJAX (e.g., with Axios).

This is super useful when you need to reload props data in your Vue component.

Example

I will create a controller method that serves an Inertia/Vue page and conditionally a JSON response.

Controller (Laravel)

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Request</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">YourModel</span>;

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">myPage</span><span class="hljs-params">()</span>
</span>{
    $data = YourModel::find(<span class="hljs-number">1</span>);

    <span class="hljs-keyword">if</span> (!$data) {
        $data = [
            <span class="hljs-string">'message'</span> => <span class="hljs-string">'Hello, Inertia and Vue!'</span>,
            <span class="hljs-string">'counter'</span> => <span class="hljs-number">0</span>,
        ];
    }

    <span class="hljs-keyword">if</span> (request()->wantsJson()) {
        <span class="hljs-keyword">return</span> response()->json($data);
    }

    <span class="hljs-keyword">return</span> inertia(<span class="hljs-string">'MyPage'</span>, [
        <span class="hljs-string">'data'</span> => $data,
    ]);
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">store</span><span class="hljs-params">(Request $request)</span>
</span>{
    $model = <span class="hljs-keyword">new</span> YourModel();
    $model->message = $request->input(<span class="hljs-string">'message'</span>);
    $model->counter = $request->input(<span class="hljs-string">'counter'</span>);
    $model->save();

    <span class="hljs-keyword">return</span> response()->json([<span class="hljs-string">'message'</span> => <span class="hljs-string">'Data saved successfully'</span>]);
}

So the method checks if the request is for JSON. If it is, respond with plain JSON; otherwise, serve the 'MyPage' Inertia/Vue view with the data.

Vue Component (MyPage.vue)

<span class="xml"><span class="hljs-tag"><<span class="hljs-name">template</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">div</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">h1</span>></span></span><span class="hljs-template-variable">{{ form.message }}</span><span class="xml"><span class="hljs-tag"></<span class="hljs-name">h1</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">p</span>></span>Counter: </span><span class="hljs-template-variable">{{ form.counter }}</span><span class="xml"><span class="hljs-tag"></<span class="hljs-name">p</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"incrementCounter"</span>></span>Increment Counter<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
  <span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">template</span>></span>

<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>></span><span class="javascript">
<span class="hljs-keyword">import</span> { ref, onMounted } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;
<span class="hljs-keyword">import</span> { useForm, usePage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@inertiajs/vue3'</span>;

<span class="hljs-keyword">const</span> { props } = usePage();
<span class="hljs-keyword">const</span> form = useForm({
    <span class="hljs-attr">message</span>: <span class="hljs-string">''</span>,
    <span class="hljs-attr">counter</span>: <span class="hljs-number">0</span>
});

<span class="hljs-keyword">const</span> incrementCounter = <span class="hljs-function"><span class="hljs-params">()</span> =></span> {
    form.counter++;
    axios.post(route(<span class="hljs-string">'myroute'</span>), form).then(<span class="hljs-function"><span class="hljs-params">response</span> =></span> {
        <span class="hljs-keyword">if</span> (response.data?.message) {
            form.message = response.data.message;
        }
        <span class="hljs-keyword">if</span> (response.data?.counter) {
            form.counter = response.data.counter;
        }
    });
};

onMounted(<span class="hljs-function"><span class="hljs-params">()</span> =></span> {
    <span class="hljs-keyword">if</span> (props.data?.message) {
        form.message = props.data.message;
    }
    <span class="hljs-keyword">if</span> (props.data?.counter) {
        form.counter = props.data.counter;
    }
    <span class="hljs-comment">// Reload data (needed for back button)</span>
    axios.get(route(<span class="hljs-string">'myroute'</span>)).then(<span class="hljs-function"><span class="hljs-params">response</span> =></span> {
        <span class="hljs-keyword">if</span> (response.data?.message) {
            form.message = response.data.message;
        }
        <span class="hljs-keyword">if</span> (response.data?.counter) {
            form.counter = response.data.counter;
        }
    });
});
</span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></span>

The Vue Component uses the onMounted lifecycle hook to fetch data via Axios when the page loads, ensuring that the component works seamlessly even when the page is served from the browser cache, e. g. when using the browser's back button.

Leave a Reply

Your email address will not be published. Required fields are marked *