Closed tanthammar closed 4 years ago
Hi @tanthammar if you can please share some of your page component, at least props and the top level form
or the container element that wraps the form.
In the meanwhile one thing you can try is adding a ':key
prop to your form element to force Vue re-render the form if some props changes:
<form :key="organizer.id" ...>
...
</form>
Other thing to check: you might be copying the organizer fields to an object in the component's data()
so you can use v-model
in your form.
As the page component is kept the same the component won't run the data()
when just the prop changed. So you can use the updated()
life-cycle hook to update the form fields, if that is the case.
For example:
<script>
export default {
props: ['organizer'],
data() {
return {
name: this.organizer.name,
address: this.organizer.address,
};
},
updated() {
this.name = this.organizer.name;
this.address = this.organizer.address;
},
}
</script>
If you have more props that can change from the server prefer using a watcher over using the updated()
lifecycle hook, otherwise you user can loose edits:
<script>
export default {
props: ['organizer'],
data() {
return {
name: this.organizer.name,
address: this.organizer.address,
};
},
watch {
organizer(current, old) {
if (current.id === old.id) return; // same organizer -> ignore
// different one -> update form fields
this.name = current.name;
this.address = current.address;
},
},
}
</script>
Hope any of that helps.
EDIT updated watcher code
Just to add this is a common situation as Vue tries to reuse the components if only the props are changed. As in a form we generally copy the props to local state this can be out of sync.
Vue router even has some docs on how to workaround this:
https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
The solution I like the most is using the :key
special prop so all the component gets re-rendered. One issue with that is that if your form is in your top level page component, you should use it on the page component itself which would require tweaking the App
render function.
One solution I use for keeping the inertia's page component without the :key
but use the :key
to reload a form, is to move the form to a component of its own, and add the :key
on its parent component.
For example, in your Organizers/Edit.vue
component you would have:
<template>
<OrganizerForm :key="organizer.id" :organizer="organizer" />
</template>
<script>
export default {
props: ['organizer'],
};
</script>
And in this new OrganizerForm
component you would place the form's code. (For code brevity I am assuming the OrganizerForm
component is imported globally)
When the organizer's id is changed, Vue will re-render the OrganizerForm
component so re-executing any logic on its data()
.
This way, you would not have to deal with watchers and life-cycle hook and could rely on Vue to keep track of your form.
Another advantage of this approach is that you could reuse this OrganizerForm
component for both Organizers/Create
and Organizers/Edit
pages.
Thank you for your fast replies!! I will read them through and test your suggestions.
But before that, I noticed in vue dev tools that Inertia does not update the url:prop nor is the "organizer" prop populated with new data from the backend controller.
This makes me wonder if that there is more to it? Shouldn't Inertia update the page props?
Now I tried to add :key to all elements where applicable. Problem remains. Simplified structure is
Organizer/Edit.vue has 14 form components, they are all inside "tabs" Example:
<q-tab-panel name="invoicing">
<invoicing
v-if="tab =='invoicing'"
:post="organizer"
:uuid="organizer.uuid"
:routeName="routeName"
:key="organizer.uuid"
/>
</q-tab-panel>
Components are lazy-loaded Example
components: {
Invoicing: () => import("@/Shared/Forms/Invoicing"),
}
I also added the key
prop to the form tag
Example in Invoicing.vue
<standard-form :key="uuid" dusk="invoicing-detailed-form" :fields="json" :uuid="uuid" :routeName="routeName" action="invoicing" />
How about Inertia page props. Should they not be updated when the url changes?
Out of ideas why. The only thing that cross my mind is to be something on Laravel side.
When making the request from Vue are you using a partial reload?
https://inertiajs.com/requests#partial-reloads
Can you check the response on your browser's console to check if the server is sending the data you expect?
The server is sending the correct data and I am not using partial reloads.
But BIG BIG thank you for all your efforts.
You're welcome =) Hope someone has some other idea to help you find the solution to this issue.
Hi @tanthammar !
Have you had a look at the PingCRM example?
I tried adding a link that links to the next organization's edit page, and it works fine.
Could it be that you're not using the model property to make the data responsive?
From PingCRM:
<template>
<form @submit.prevent="submit">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.name" :errors="$page.errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :errors="$page.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :errors="$page.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :errors="$page.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :errors="$page.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :errors="$page.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :errors="$page.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" />
<option value="CA">Canada</option>
<option value="US">United States</option>
</select-input>
<text-input v-model="form.postal_code" :errors="$page.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center">
<button v-if="!organization.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Organization</button>
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update Organization</loading-button>
</div>
</form>
[...]
</template>
<script>
export default {
props: {
organization: Object,
},
data() {
return {
sending: false,
form: {
name: this.organization.name,
email: this.organization.email,
phone: this.organization.phone,
address: this.organization.address,
city: this.organization.city,
region: this.organization.region,
country: this.organization.country,
postal_code: this.organization.postal_code,
},
}
},
[...]
}
</script>
In Edit.vue I receive the page props
props: {
organizer: {
type: [Object, Array]
},
I pass it down to the component that defines the form fields (there are 14 form components...)
<q-tab-panel name="invoicing">
<invoicing
v-if="tab =='invoicing'"
:post="organizer"
:uuid="organizer.uuid"
:routeName="routeName"
:key="organizer.uuid"
/>
</q-tab-panel>
The field components are lazy-loaded Maybe this is the problem ??
components: {
Invoicing: () => import("@/Shared/Forms/Invoicing"),
}
In Invoicing.vue I use the organizer
prop (renamed to post
) to populate form data
props: {
post: {
type: [Object, Array]
},
A field definition example
data() {
return {
fields: {
business_no: {
name: "business_no",
value: this.post.business_no,
label: this.$trans("fields.business_no"),
hint: this.$trans("fields.business_no_hint"),
type: "text"
},
The field definition component passes the fields
as props to a form builder component
The organizer
prop is not passed to the form component. (Could this be the problem?)? The form only receives initial field values, a route and a model uuid,
<standard-form dusk="invoicing-basic-form" :fields="fields" :uuid="uuid" :routeName="routeName" action="invoice_cols" />
StandardForm.vue
props: {
fields: {
type: [Object, Array],
required: true,
}
},
data() {
return {
form: this.fields,
}
},
Simplified form
<template>
<form>
<div v-for="(field, index) in form" :key="index">
<input v-model="field.value" :name="field.name" />
</div>
<button label="save" type="submit" color="primary" />
<form>
</template>
I did another test today. Instead of passing down the "organizer" prop from the page through all child components I tied it to $page. And It almost worked ...
Before:
|-Edit.vue, props: "organizer" = $page.organizer
|-<invoicing :post=organizer />
After
|-Edit.vue, props: "organizer" = $page.organizer
|-<invoicing /> props: "post" = $page.organizer
The initial v-model data, in the form fields are updated.
But I got errors in console.
There is something going on with Inertia destroying components.
The console is full with errors from components not being able to destroy their listeners on Vue
beforeDestroy
hook.
I guess that is another topic.
Hey @tanthammar!
We're trying to clean up some old issues. Do you know if this problem is still relevant?
@reinink I'm running into a similar issue when I submit the PUT request and redirect back to a form. Maybe I'm doing something wrong here but here's the code and steps for setup and reproducing the issue. https://github.com/IvanBernatovic/inertia-vue-issue
I reuse forms by extracting them into a separate component, that way I use the same component for create and edit form. If I put :key="Math.random().toString(36)"
on the form component, that fixes the issue. Btw. I'm using Laravel Jetstream app in the example but it shouldn't matter.
@IvanBernatovic So, this is actually being caused by Jetstream, not Inertia.js. It's because Jetstream defaults form submission to { resetOnSuccess: true }
, which is actually problematic if you submit a form back to the same page. Basically what happens is Jetstream captures the props on the initial component load and saves them in local memory. Then, when you make a request to submit the form, it returns back to the same component, which still has those original values in memory, and Jetstream restores those values.
Update your UserForm.vue
file to fix this:
form: this.$inertia.form({
name: this.user?.name || "",
email: this.user?.email || "",
}, {
resetOnSuccess: false,
}),
IMHO, Jetstream shouldn't default resetOnSuccess
to true
, but rather this should be an opt-in thing you use when it's needed (ie. resetting password inputs back to blank).
So, I am going to close this issue, because I actually think I remember what was wrong here, and it's been fixed.
The issue had to do with component state when making GET
requests to the same component. Ping CRM actually suffered from this bug originally as well. This was fixed by introducing the preserveState feature.
My only concern is that this issue was posted long after that feature was added. The only thing I can think is that @tanthammar maybe was using an older version of Inertia.
If someone is able to provide a repo that reproduces this issue, I'd be more than happy to have another look at this. 👍
@reinink Thanks for the quick response. That was it so thank you very much, and it was Jetstream after all.
If I am on an the
edit
page for Organizer A and click on an inertia link toedit
Organizer B. Inertia replaces the url but the form is not reloaded and keeps Organizer A data (and vue blows up) (If I click on the link from any other page it works as expected.)Example href: https://test-site.test/app/organizers/org-YSMqnF-L8VSq8s5jzZ-nB/edit
OrganizerController@edit