Remix run - submitting an action and getting errro "root" - does not have an action, but you are trying to submit to it - remix.run

Im having a bit of trouble getting my action to dispatch in remix run - I have an Aside which comes out with all the data from my shopping cart - I have a form that collates all the data - and when I want the checkout to be created I want to call the action
<Form action='/products' method="post">
{cart.map((item, idx) => (
<div key={idx}>
<input readOnly value={item.product.id} type="hidden" name="id"/>
<input readOnly value={item.quantity} type="hidden" name="quantity"/>
</div>
))}
<button
className="mr-2 m"
> Add to Cart
</button>
</Form>
export const action: ActionFunction = async ({request}) => {
// get the form data from the POST
const formData = await request.formData()
const id = formData.getAll('id')
const quantity = formData.getAll('quantity')
const newObj = id.map((data, index) => {
return { variantId: data, quantity: quantity[index] }
} )
const cart = await createCheckout(newObj)
return cart
}
From the data that is requested here my checkout URL is generated so i need to wait for the response. When I submit i get a 405 error saying method not allowed
react_devtools_backend.js:4026 Route "root" does not have an action, but you are trying to submit to it. To fix this, please add an `action` function to the route
This is the error message but I cant seem to find anywhere in the docs how to add a action function to the route? because I swear I am already doing this?

tldr;
I ran into this same issue and was able to solve by changing my action url to include ?index at the end.
Details
My "products" file was located at /products/index.tsx
In order for remix to not think I was referring to root I had to use the following action route:
action="/products?index"
Just using action="/products" alone did not work.
Once I added the ?index part to the action, everything worked as expected.
According to the remix docs:
If you want to post to an index route use ?index in the action: action="/accounts?index" method="post" />
For more details, see: https://remix.run/docs/en/v1/api/remix#form
Also, note that most of the time you can just leave off the action and the Form will use the route in which it is rendered.

Related

Why is record not deleting and response is 303 in Laravel 9, Inertia and Vue3?

I have following code in route web.php file.
Route::resource('dailyrevenue', DailyRevenueController::class)->middleware('auth');
Then in my DailyRevenueController.php
public function destroy(DailyRevenue $revenue)
{
$revenue->delete();
return redirect()->back();
}
And in my vue3 code:
const submit = function (id) {
const check = confirm("Are you sure to delete ?")
if (check) {
Inertia.delete(route('dailyrevenue.destroy',id), {
id: id,
method: 'delete',
forceFormData: true
})
}
}
Finally in the template:
<template #cell(actions)="{item: tax}">
<form method="delete" #submit.prevent="submit(tax.id)">
<input type="hidden" name="_method" value="delete"/>
<button type="submit">Delete</button>
</form>
</template>
Now request reaches to the method. But nothing is deleted. The request sent is DELETE request. Instead of 302 response it sends back 303 (See others).
Community help is appreciated.
Thanks.
I found the solution. The reson behind it was the variable name. In url endpoint the variable name was decleared as dailyrevenue and in the method as $revenue.
You can find your url variable by typing php artisan route:list.
I found that the url variable name must match with variable name in method.
Hope this helps others too.

Remix.run - common shared components

I’m just getting started learning remix.run and whilst I’ve gone through the tutorials there’s one bit I’m stuck on how I should implement it in remix.
If I wanted to display a common header that might toggle a sign in/sign out button based on the users logged in state where would this live?
My nextjs thinking would be to create the components and reference them in the common document. I know I can do this in the remix.server and remix.client files, but as my “login” component is and isn’t a route (I.e I might want to POST to the route when a user submits the login form but GET /login isn’t really a route) how would you structure something like this and would doing this even allow me to have loader and action functions in the shared component?
Do I just need to adjust my thinking about how to achieve this in remix or am I overthinking it and the above is perfectly valid?
I tried the following and it works. But then I end up just creating an empty "logout" route to process the form data with an action and loader that process the form in the case of the action or just redirect if a GET request via the loader. Is this the best approach?
export const SignIn = ({user}) => {
return (
<>
<form method="POST"action="/logout">
<input type="hidden" id="some" value="foo" />
{user ?
(
<button>sign out</button>
)
: (
<button>sign in</button>
)
}
</form>
</>
)
}
Thanks
based on https://remix.run/docs/en/v1/tutorials/jokes#build-the-login-form
it does indeed look like an empty route for logout:
import type { ActionFunction, LoaderFunction } from "#remix-run/node"
import { redirect } from "#remix-run/node"
import { logout } from "~/utils/session.server"
export const action: ActionFunction = async ({request}) => {
return logout(request);
};
export const loader: LoaderFunction = async () => {
return redirect("/");
};

Laravel Livewire, form submit: do something first then submit the form

I have a livewire form which I intent to submit to an external url. However, before submitting, I want to programmatically add some hidden inputs which the user should not be able to edit then finally submit the form:
<form action="some-external-url" wire:submit.prevent="processForm" method="post">
<x-inputs.text-input wire:model="amount" name="amount" />
<x-inputs.button title="Submit" />
</form>
Something similar to this jQuery code:
$('form').submit( function(ev){
ev.preventDefault();
//fetch and add some additional fields to the form
// finally submit the form
$(this).unbind('submit').submit()
});
How best can I achieve this using livewire. Please note that I dont intent to use guzzle to submit this form.
If you want in your component you can set the properties, you need and then in your mount method, you initialise the value for those properties.
see forms in livewire
class ComponentName extends Component
{
public $hidden_val;
public function mount()
{
$this->hidden_val = "my_hidden_val";
}
}
Then pass it with livewire
<input type="hidden" wire:model="hidden_val">
But I also think as #ClémentBaconnier and would suggest to pass to external link the data of form using Http Client provided by laravel in your controller or event within your livewire component.
Http::asForm()->post('some-external-url', ['form_data' => /*your form data*/]);
Follow it here

Get Data back from Vue Component

Is it possible to get Data back from a vue component?
Laravel blade.php code:
...
<div>
<component1></component1>
</div>
...
In component1 is a selectbox which i need (only the selected item/value) in the blade.php
A vue component, when rendered in the browser, is still valid HTML. If you make sure your component is wrapped in a form element and has a valid input element, and the form can be submitted, the PHP endpoint can consume the form’s data without problems. It could look like this:
Layout/view:
<form method="post" action="/blade.php">
<component1></component1>
<button type="submit">Submit form</button>
</form>
Component (<component1/>):
<fieldset>
<input type="checkbox" name="my_option" id="my_option">
<label for="my_option">I have checked this checkbox</label>
</fieldset>
PHP script (blade.php):
echo $_POST["my_option"] // if checked, should print "on"
If you are looking for a JavaScript centered approach, you may want to serialize the form and fetch the endpoint; it could look similar to this:
import serialize from 'form-serialize';
const formData = serialize(form)
fetch(form.action, { method: 'POST' }, body: JSON.stringify(formData) })
.then(response => {
// update page with happy flow
})
.catch(error => {
// update page with unhappy flow
})
Building from an accessible and standardized basis using proper HTML semantics will likely lead to more understandable code and easier enhancements down the road. Good luck!
(Edit: if you require a complete, working solution to your question, you should post more code, both from the Vue app as well as the PHP script.)

how to clear validation errors for Angular Material mat-error

I'm trying to reset a form after I've added a value.
Form Code Snippet
<form [formGroup]="addAttributeForm" fxLayout="column">
<mat-form-field>
<input matInput formControlName="title" placeholder="Title" required>
<mat-error>This field is required</mat-error>
</mat-form-field>
</form>
In the component
onSubmit(form: FormGroup) {
// do work
form.reset();
}
What I'm observing:
The form values are set to empty.
But the validation messages are still displayed from mat-error.
I've tried form.markAsPristine(), form.markAsUntouched() and combining all three.
How can I reset the form so the mat-error is not displayed?
The form group has no "knowledge" about whether the actual HTML form has been submitted or not. It only keeps track of the form values/validity/enabled state. So resetting the form group does reset the values but not any state regarding whether the form has been submitted.
To do this, you need to get a hold of the FormGroupDirective and call resetForm() on it.
Form Code Snippet
<form [formGroup]="addAttributeForm" fxLayout="column">
<!-- ... -->
</form>
In the component
#ViewChild(FormGroupDirective) formDirective: FormGroupDirective;
onSubmit(form: FormGroup) {
// do work
this.formDirective.resetForm();
}
This solution works with me.
You need to do next:
formReset(form: FormGroup) {
form.reset();
Object.keys(form.controls).forEach(key => {
form.get(key).setErrors(null) ;
});
}
This reset the form and clear all error.
The only way I've been able to successfully do this is by setting a flag to hide the form when you want to reset it and then using a timeout to set that flag back to true. As far as I know, there is no built-in way to do this yet.
showForm = true;
reset(): void {
this.showForm = false;
setTimeout(() => this.showForm = true);
}
And then in the HTML on the form element use *ngIf="showForm".

Resources