Livewire: Easy access to all form-data after form submit

Created on 4 Oct 2019  路  17Comments  路  Source: livewire/livewire

Right now we can easily create interactive forms by using wire:model on a variety of input elements. Even nested properties are now supported so we can put all the interactive form-data elements in an array if we want. So far so good.

No problem at all if we are creating LiveWire components that are specifically made for hard coded forms.

But what if some of the input elements in the form do not need to be reactive? How do we get to those values in the component? wire:model on them is a little to much. We dont want an ajax request every time they change. We only want to know the value once the form is submitted.

There is also the case of meta-data programming. What if we do not want to hard-code the form, but create it based on metadata? In that case the LiveWire component should be more dynamic.

What I am looking for is a way to retrieve all the form-data at once in the LiveWire component.

Right now i just haven't found a good way of doing this. Maybe I am just not seeing the obvious. Maybe there is already some sort of workaround?

At the least I would want to send the form-data with the submit-event on the form level.

Anyone any ideas?

Most helpful comment

I totally agree. This would be very helpful. I'll spit out some API ideas:

Ok, here's a way you could actually do this right now:

<form wire:submit.prevent="submit(Object.fromEntries(new FormData($event.target)))">
    <input type="text" name="foo">
    <input type="text" name="bar">

    <button>Submit</button>
</form>

Then, in your component, here's the submit method:

public function submit($formData)
{
    dd($formData);
}

After typing in "baz" and "bob" into the inputs and submitting, this will output:
image

This will work in the meantime. But there are two things this lacks:

  1. This won't handle nested data (like name="foo[a]")
  2. This feels hacky and ugly

Here are a three proposals for different APIs to supports this:

Proposal A:

Pass the form data directly to the method by default (if no other parameters are specified)

View

<form wire:submit="submit">

Component

public function submit($data)

Proposal B:

Add a magic variable to get the form data and pass it in.

View

<form wire:submit="submit($formData)">

Component

public function submit($data)

Proposal C:

A custom directive that's in charge of handling forms. There is extra potential here for custom behavior on the backend and frontend? Maybe there would be a convenient way to validate against this data, or use a form object or something.

View

<form wire:form="foo">

Component

public function onFooSubmit($data)

Currently, B is my favorite and could be implemented fairly easily. But obviously C has some serious potential. But maybe we wait until this idea is fleshed out further to move to something like C

Thoughts?

All 17 comments

I totally agree. This would be very helpful. I'll spit out some API ideas:

Ok, here's a way you could actually do this right now:

<form wire:submit.prevent="submit(Object.fromEntries(new FormData($event.target)))">
    <input type="text" name="foo">
    <input type="text" name="bar">

    <button>Submit</button>
</form>

Then, in your component, here's the submit method:

public function submit($formData)
{
    dd($formData);
}

After typing in "baz" and "bob" into the inputs and submitting, this will output:
image

This will work in the meantime. But there are two things this lacks:

  1. This won't handle nested data (like name="foo[a]")
  2. This feels hacky and ugly

Here are a three proposals for different APIs to supports this:

Proposal A:

Pass the form data directly to the method by default (if no other parameters are specified)

View

<form wire:submit="submit">

Component

public function submit($data)

Proposal B:

Add a magic variable to get the form data and pass it in.

View

<form wire:submit="submit($formData)">

Component

public function submit($data)

Proposal C:

A custom directive that's in charge of handling forms. There is extra potential here for custom behavior on the backend and frontend? Maybe there would be a convenient way to validate against this data, or use a form object or something.

View

<form wire:form="foo">

Component

public function onFooSubmit($data)

Currently, B is my favorite and could be implemented fairly easily. But obviously C has some serious potential. But maybe we wait until this idea is fleshed out further to move to something like C

Thoughts?

Nice to see we're following the same line of thinking:-) I was initially also thinking about options A and B. But to me they both felt a little bit hacky

Before you know it there are magic variables everywhere. It doesn't feel clean. And listening to your podcasts, i think you feel the same. It is not intuitive. You need to check the docs.

I commented yesterday or the day before on an issue that someone hadn't read the docs about key() in the docs ...... I think keeping the LiveWire API as simple as possible will help adoption. I still see tremendous potential in LiveWire.

About your option C, I was thinking more in the line of wire:model on the form element. That feels clean. But........ wire:model is all about 2-way data-binding. Is that something you would consider? I am not talking about implementation yet. We could even use the same modifiers on the form level.

I probably would use wire:model.lazy="formData" on the form level and would expect my inputs to be initialized from this array on the component and updated on every change event.

For more dynamic forms this could be a life saver. We could add input elements and change events on the new inputs could be handled generically.

That said. I remember a discussion in the early days of Angular 2 about data binding and the api/interface. They split the binding in two ways. In other words there is a separate API for up- and downstream. On top of that there is an API that combines the two for easy 2-way data binding. Does that make sense? Just an idea to make the API more consistent.

Last but not least about your proposed workaround. It feels hacky to you, you say. Well, yes and no.
We cannot support everything in an API. There comes a time you have to resort to the javascript side of things :-) What I understand is that LiveWire supports inline javascript? form.submit="<javascript that will be evaluated>"

If this was documented, well, than it doesn't feel hacky to me. It feels powerfull!

@calebporzio Just some thoughts on a nice Dutch friday evening ........

I probably would use wire:model.lazy="formData" on the form level and would expect my inputs to be initialized from this array on the component and updated on every change event.

I would love a feature like this. For me I care less about binding individual inputs, and more about easily binding our entire forms. Once the user submits the form then all of the inputs are passed to the controller. Would be very interesting to see what a Livewire form model could open up for validation/loading states as well.

All these thoughts make good sense. Thanks for the feedback. Does anyone have ideas of what attaching wire:model to form element would look like in the backend?

Some initial thoughts:
would wire:model="foo", bind to a public property called $foo that's just a plain array of data? OR will $foo be a Livewire object called "LivewireForm" or something that get's hydrated. How ill someone validate against the form? How would we show specific validadtion errors next to input elements. Will the dev need to list out every single input name in the backend to prevent mass assignment stuff?

There's a lot of question marks here for me.

@calebporzio I'm not exactly sure how this could work, but would it be plausible to leverage the existing request object somehow? Or a Livewire specific request object?

For example, if a user submits a form with wire:form="store" or wire:submit="store", a request object will be passed to the controller function store(Request $request). The $request would contain all of the inputs that were submitted with the form, regardless if they have public properties or not.

I haven't spent much time looking at how Livewire connects everything behind the scene so this may simply not be possible. I'll try to dive in more this week.

Using forms this way solves my issue #117 after some refactoring. So this would be great!

Hey @bdweix - that's a really interesting suggestion. It would be a HUGE win in terms of usability. The question is, do we want to "fake" the Laravel request.

We would have to work some black magic (totally possible), but we have to decide if it would be too much magic.

Hey @jwktje - can you expound on how this solves your problem? Thanks, it's just hard to context switch, and you spelling it out for me will be much easier. Thanks!

@calebporzio - I agree I think faking a Laravel request would be too confusing. Instead of "faking" it, I think you could potentially provide access to the original request that Livewire grabs. This may require some moving of the Livewire data into a single field livewire to avoid confusion.

That way if I attached Livewire to a form and did dd($request), all of the form inputs are visible inside of the request like normal. I would also presumably see the Livewire information, which could be very helpful towards understanding some of the magic of Livewire.

I will think about this a bit more, especially regarding validation/responses/etc. If you're looking for podcast/docs ideas - a section dedicated to the "Request Lifecycle of Livewire" showing what happens through the code when something is bound to wire:model would be extremely helpful. I'm sure you've got endless work on your plate already though and thank you for spending so much time on all this!

@calebporzio Sure! The refactor below results in the expected behavior that I mention in my issue #117
chat.blade.php*

<div>
    <div class="messages-wrap" wire:poll.5000ms="$refresh">
        @foreach ($messages as $message)
            <div>{{$message->text}}</div>
        @endforeach
    </div>
    <form wire:submit.prevent="newMessage(Object.fromEntries(new FormData($event.target)))" wire:ref="send-message">
        <input type="text" name="message" wire:loading.attr="disabled" wire:target="send-message">
        <button>Send</button>
        <button id="clearMessages" wire:click="clearMessages">Clear</button>
    </form>
</div>

Chat.php

namespace App\Http\Livewire;
use Livewire\Component;
use App\Messages;

class Chat extends Component
{
    public function render()
    {
        return view('livewire.chat',['messages' => Messages::all()]);
    }
    public function newMessage($formData)
    {
        if($formData['message']) {
            Messages::create(['text' => $formData['message']]);
        }
    }
    public function clearMessages()
    {
        Messages::truncate();
    }
}

Is there a way to send a hidden field?

I want to know wich ID on DB I want to update, but the hidden field isnt sent to the controller.

If you have another way to do it, would be very cool.

Thank you

Closing this for now. Unless more people really pull for a $formData magic value, OR automatically sending it along on submit.

I totally agree. This would be very helpful. I'll spit out some API ideas:

Ok, here's a way you could actually do this right now:

<form wire:submit.prevent="submit(Object.fromEntries(new FormData($event.target)))">
    <input type="text" name="foo">
    <input type="text" name="bar">

    <button>Submit</button>
</form>

Then, in your component, here's the submit method:

public function submit($formData)
{
    dd($formData);
}

After typing in "baz" and "bob" into the inputs and submitting, this will output:
image

This will work in the meantime. But there are two things this lacks:

  1. This won't handle nested data (like name="foo[a]")
  2. This feels hacky and ugly

Here are a three proposals for different APIs to supports this:

Proposal A:

Pass the form data directly to the method by default (if no other parameters are specified)

View

<form wire:submit="submit">

Component

public function submit($data)

Proposal B:

Add a magic variable to get the form data and pass it in.

View

<form wire:submit="submit($formData)">

Component

public function submit($data)

Proposal C:

A custom directive that's in charge of handling forms. There is extra potential here for custom behavior on the backend and frontend? Maybe there would be a convenient way to validate against this data, or use a form object or something.

View

<form wire:form="foo">

Component

public function onFooSubmit($data)

Currently, B is my favorite and could be implemented fairly easily. But obviously C has some serious potential. But maybe we wait until this idea is fleshed out further to move to something like C

Thoughts?

@calebporzio
Need Help!!
Not working for select input with multiple option enabled. Other inputs are working fine.

@calebporzio Sure! The refactor below results in the expected behavior that I mention in my issue #117
chat.blade.php*

<div>
    <div class="messages-wrap" wire:poll.5000ms="$refresh">
        @foreach ($messages as $message)
            <div>{{$message->text}}</div>
        @endforeach
    </div>
    <form wire:submit.prevent="newMessage(Object.fromEntries(new FormData($event.target)))" wire:ref="send-message">
        <input type="text" name="message" wire:loading.attr="disabled" wire:target="send-message">
        <button>Send</button>
        <button id="clearMessages" wire:click="clearMessages">Clear</button>
    </form>
</div>

Chat.php

namespace App\Http\Livewire;
use Livewire\Component;
use App\Messages;

class Chat extends Component
{
    public function render()
    {
        return view('livewire.chat',['messages' => Messages::all()]);
    }
    public function newMessage($formData)
    {
        if($formData['message']) {
            Messages::create(['text' => $formData['message']]);
        }
    }
    public function clearMessages()
    {
        Messages::truncate();
    }
}

How do you validate this data ?

@jiteshdhamaniya I am playing with this at the moment and have used something like this:

try {
      $validator = Validator::make($formData, ['name' => 'required']);
      $validator->validate();
} catch (ValidationException $e) {
      return redirect()->back()->withErrors($validator->errors())->withInput($formData);
}

Literally started playing with LiveWire today, so no idea if that is the best way but might help.

Disappointing that this was closed. The example given in https://github.com/livewire/livewire/issues/313#issuecomment-538401606 no longer seems to work.

Disappointing that this was closed. The example given in #313 (comment) no longer seems to work.

I literally just implemented this and seems to work just fine.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nickpoulos picture nickpoulos  路  3Comments

roni-estein picture roni-estein  路  3Comments

austenc picture austenc  路  3Comments

bardh7 picture bardh7  路  3Comments

pmartelletti picture pmartelletti  路  3Comments