Using POST in React without form? - javascript

Lets say I have this code here:
getInitialState:function(){
return { food: 'Chinese' }
},
restaurants:function(){
return (<div><form method="post">
<p>I like <span name="food">{this.state.food}</span> food</p>
<button type="submit">Butane</button>
</form></div>);
},
My only experience with POST so far has been with forms and input fields. So I would like to know how to do this without, using more static content.
In the above example, I have content that isn't derived from an input field. I would like to put the state variable, in this case, Chinese, into a POST request.
Ideally, the button labeled butane submits the info from my state into my POST. And the span name is there to assign it a name for my back-end to read it from.
How would I re-arrange this code to enable use of the state variable in a POST context?

You can add hidden input into form
<div>
<form method="post">
<p>I like <span name="food">{this.state.food}</span> food</p>
<button type="submit">Butane</button>
<!-- Hidden fields -->
<input type="hidden" value={this.state.food}/>
</form>
</div>
Update
Agree with #Rishat to use AJAX call.
For another situation which you want to do a normal POST request but don't want to add any input field to your form. You can use this solution:
JavaScript post request like a form submit

Since you're working with React, chances are you develop a single-page application that doesn't reload nor does it lead a user to another location. To perform a POST request then, you need to do it asynchronously. One of the convenient ways is to use axios for that. The whole component would look like this:
import React, { Component } from 'react';
import axios from 'axios';
class X extends Component {
constructor() {
super();
this.state = {
food: 'Chinese'
};
}
handleSubmit(event) {
const {
food
} = this.state;
event.preventDefault();
// do something with form values, and then
axios.post('https://your/api/endpoint', {
food // + any other parameters you want to send in the POST request
}).then(response => {
// do something with response, and on response
}).catch(error => {
// do something when request was unsuccessful
});
}
restaurants() {
return (
<div>
<form
method="post"
onSubmit={event => this.handleSubmit(event)}>
<p>I like <span name="food">{this.state.food}</span> food</p>
<button type="submit">Butane</button>
</form>
</div>
);
}
}

Related

What is better to use to communicate with a child component - $emit or $ref?

Tell me, please.
There is a page for changing user data. This page consists of fields for entering information about the user and a Submit button.
I need to create a popup component that performs the same functions.
The form itself will now be in a child component. The submit button is now moved to the parent component.
Therefore, I need to pass the entered data to the parent component. Everything is complicated by the fact that initially data comes from the server with previously entered information about the user. Therefore, initially you need to transfer data to the child component through props, and then, when changing them from the child, transfer it to the parent.
But what if there are a lot of variables?
For example: I need to create 15 props and then pass this data through $emit. The data itself is passed either through the #input event of the <input> tag or the #change event of the <select> tag.
I thought of three options, but I don't know which one is better. Or maybe some should not be used at all.
Using $emit
Parent component
<template>
<Child
:first-props="user.first"
:second-props="user.second"
....
:fifteenth-props="user.fifteenth"
/>
<button #click='submit'>Submit</button>
</template>
<script>
import Child from '#/components/Child.vue'
import { mapGetters } from 'vuex'
export default {
data: () => ({
first: '',
second: '',
...
fifteenth: ''
}),
components: {
Child
},
computed: {
...mapGetters({
user: 'user/getUser'
})
},
methods: {
submit() {
//Sending data
}
},
mounted: {
this.$store.dispatch('user/getUserData')
}
}
</script>
Child component
<template>
<div>
<input type="text" value="first" #input="username" />
<input type="text" value="second" #input="name" />
<input type="text" value="fifteenth" #input="surname" />
</div>
</template>
<script>
export default {
props: {
first: {
type: String,
required: true
},
first: {
type: String,
required: true
},
...
fifteenth: {
type: String,
required: true
}
},
methods: {
username() {
this.$emit('changeUsername', first)
},
name() {
this.$emit('changeName', second)
},
surname() {
this.$emit('changeSurname', fifteenth)
}
}
}
</script>
In this variant, I am worried about the number of props. I do not know if this can somehow affect the speed and quality of the component.
Using $ref
Parent component
<template>
<Child
ref='childComponent'
/>
<button click="submit">Submit</button>
</template>
<script>
import Child from '#/components/Child.vue'
export default {
data: () => ({
first: '',
second: '',
...
fifteenth: ''
}),
components: {
Child
},
method: {
submit() {
this.$refs.childComponent.submit()
}
}
}
</script>
Child component
<template>
<div>
<input type="text" v-model="first" #input="username" />
<input type="text" v-model="second" #input="name" />
<input type="text" v-model="fifteenth" #input="surname" />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data: () => ({
first: '',
second: '',
...
fifteenth: ''
}),
computed: {
...mapGetters({
user: 'user/getUser'
})
},
methods: {
submit() {
//Sending data
}
},
mounted: {
this.$store.dispatch('user/getUserData')
}
}
</script>
In this variant, there is not much interaction between the parent and child component. And the parent component fires only the submit method. Plus, you don't need to change the existing code too much, as you just need to move the button. But here I am concerned: is it not a bad decision to apply ref in this way ? And the second problem is the availability of data when the button is clicked. It is more likely that the child component has not yet received the necessary data or simply has not loaded yet, and the user has already pressed the button.
For example, the parent component already has a main title and a submit button. But the child component is still loading.
Although the solution here can be to make the button available for pressing only after loading the child component using :disabled="isDisabled".
Using vuex
This option immediately seems wrong to me, since the data will be used only inside this component. And vuex seems to me suitable only for global cases.
I have tried all three options and they all work. But I wanted the code not only to work, but also to be correct.
$ref and $emit don't serve the same purpose, there's generally no choice between them.
Option 1 can use a single data prop in a child:
<input
type="text"
:value="formData.first"
#input="$emit('updateFormData', { key: 'first', value: $event.target.value }))"
/>
And merge the changes in a parent:
<Child
:formData="formData"
#updateFormData="formData[$event.key] = $event.value"
/>
Option 2 is possible in its current state. Alternatively, a child can be made self-contained and contain submit button, then there is no use for $ref. It can emit submit event in case there's business logic associated with form submit that shouldn't be moved to a child.
Option 3 is possible but the use of global store for local purposes is not justified, local stores can be achieved with Vuex dynamic modules.

How can I create a reusable form component for each resource I create and/or update (Vue 3, vue-router, Pinia)

I have a Vue 3 app using Pinia stores that CRUD's data from my rest API. I've just started working with Vue 3 (from smaller vue 2 projects) and this is my first time using Pinia, so I'm still learning the intricacies of both.
One resource I manage from my api is called Applications, and I have a composable that manages API calls to retrive all apps, 1 app, or update the selected app. Instead of creating a form component to UPDATE, and a form component to CREATE applications, I'd like to create a single form component that handles both. So far I can populate my form with an existing application using a route that contains an application_id, and I create a new application if no application_id is in my route.params. I'm just not sure how to tell the form "Hey lets update this application instead of creating it.". I thought of using v-if directives that each create a <button> (one to run update, one to run create method) depending on there is an application_id in my route.params, but that seems inefficient (it may be correct, I'm just lacking knowledge). Here's my code:
// ApplicationStore.js (pinia store)
import { defineStore } from "pinia";
// Composable for axios API calls
import { getApplications, getApplicationByID, createApplication } from "#/composables/applications";
export const useApplicationStore = defineStore("application", {
state: () => ({
applications: [], //list of applications from database
application: {}, //currently selected application for edit form
loading: false,
success: "Successfully Created",
error: "",
}),
getters: {},
actions: {
async fetchApplications() {
this.loading = true;
this.applications = [];
const { applications, error } = await getApplications();
this.applications = applications;
this.error = error;
this.loading = false;
},
async fetchApplicationByID(id) {
this.loading = true;
const { application, error } = await getApplicationByID(id);
this.application = application;
this.error = error;
this.loading = false;
},
async createNewApplication() {
this.loading = true;
const { application, results, error } = await createApplication(this.application);
this.application = application;
this.error = error;
this.loading = false;
if (results.status === 201) {
// show this.success toast message
}
}
}
});
Here is my ApplicationForm component. It currently looks for route.param.id to see if an application is selected, if so it populates the form:
// ApplicationForm.vue
<template>
<section class="columns">
<div class="column">
<div v-if="error" class="notification is-danger">{{ error }}</div>
<div class="field">
<label class="label">Name</label>
<input v-model="application.name" class="input" type="text" />
</div>
<div class="field">
<label class="label">Location</label>
<input v-model="application.location" class="input" type="text" />
</div>
<div class="control">
<button #click="createNewApplication" class="button">Save</button>
</div>
</div>
</section>
</template>
<script setup>
import { useRoute } from "vue-router";
import { useApplicationStore } from "#/stores/ApplicationStore";
import { storeToRefs } from "pinia";
const route = useRoute();
const { applications, application, error } = storeToRefs(useApplicationStore());
const { createNewApplication } = useApplicationStore();
//checking if there's an id parameter, if so it finds the application from the list in the store
if (route.params.id) {
application.value = applications.value.find(app => app.id === Number(route.params.id));
} else {
//form is blank
application.value = {};
error.value = "";
}
</script>
Is there a preferred way to use this single form for both create and updates? I wonder if slots would be a good use case for this? But then I think I'd still end up making multiple form components for each CRUD operation. Also, I considered using a v-if to render the buttons based on if an application is in the store or not, like this:
<button v-if="route.params.id" #click="updateApplication" class="button">Update</button>
<button v-else #click="createNewApplication" class="button">Save</button>
I can't help but feel there is a better way to handle this (it is something I'll utilize a lot in this and future projects). This is my first big vue/pinia app. I'm loving the stack so far but these little things make me question whether or not I'm doing this efficiently.
If the form's UI is mainly expected to stay the same except for a few small differences (e.g. the button text), you could make the form emit a custom "submit" event and then handle that event from the parent component where you render the form (i.e. on the update page you have <ApplicationForm #submit="updateApplication"> and on the create page you have <ApplicationForm #submit="createNewApplication" />:
// ApplicationForm.vue
<template>
<section class="columns">
<div class="column">
<div v-if="error" class="notification is-danger">{{ error }}</div>
<div class="field">
<label class="label">Name</label>
<input v-model="application.name" class="input" type="text" />
</div>
<div class="field">
<label class="label">Location</label>
<input v-model="application.location" class="input" type="text" />
</div>
<div class="control">
<button #click="$emit('submit')" class="button">{{ buttonText }}</button>
</div>
</div>
</section>
</template>
As for the text, you can pass that as a prop (e.g. buttonText) to the ApplicationForm component. If some sections of the form are more substantially different than just different text between the "Update" and "Create" form, that's when you'd use slots.
I wouldn't recommend making the <ApplicationForm /> component responsible for reading the route parameters; that should generally be done only by the Vue component responsible for rendering the page (and then it should pass that data through props so that the component is as re-usable as possible)
So your parent component could look something like this:
<ApplicationForm v-if="application" #submit="updateApplication" />
<ApplicationForm v-else #submit="createNewApplication" />

InertiaJS keep form data after validation error

In my InertiaJS/VueJS project I have a prop that receive some data from the backend:
event: {
type: Object,
default: () => { return {} }
},
That's how the event obj looks in the backend:
['name' => 'Event Name']
I use toRefs to convert the reactive prop and update its properties in the UI:
const eventRef = toRefs(props).event
So the Event has the name 'Event Name' when the component loads, when I update the event name in the UI to 'New Name' and submit the form, I send the eventRef obj in the request to create the new event:
Inertia.post(url, eventRef, only: ['global'])
If there's a validation error in the backend, I return it to the frontend and show the error in the UI (This is working without problems). The problem I have is that Inertia (or maybe VueJS) is returning the object eventRef to his previous state when the component is created. Which means that the name property of the eventRef changes to 'Event Name' again, instead of staying with 'New Name` that was updated in the UI. I would like to preserve the state of the object after I submit the form. This is my Inertia response:
component: "Events/EventNew"
props: {
global: {} // Global object
}
url: "/app/qa/events/new"
version: null
As you can see I'm not even getting the 'event' prop from the backend, so it shouldn't be updated. After reading Inertia docs I thought that a simple preserveState:true in the request options would do the job but this is not happening. Every time the server returns an Inertia response, the eventRef obj is 'reset'.
What am I missing here? I would appreciate some help
I believe I had the same problem using Inertia with Vue2. If I understood correctly, you probably seeing this on a form where you trying to update and entry, right? Your validation is working but the form keeps resetting itself to the previous state. If that's the case, what solved this for me was this:
Instead of using Inertia.post() directly, use the Inertia Form Helper instead
Vue 2
<template>
<form #submit.prevent="form.post('/login')">
<!-- email -->
<input type="text" v-model="form.email">
<div v-if="form.errors.email">{{ form.errors.email }}</div>
<!-- password -->
<input type="password" v-model="form.password">
<div v-if="form.errors.password">{{ form.errors.password }}</div>
<!-- remember me -->
<input type="checkbox" v-model="form.remember"> Remember Me
<!-- submit -->
<button type="submit" :disabled="form.processing">Login</button>
</form>
</template>
<script>
export default {
data() {
return {
form: this.$inertia.form({
email: null,
password: null,
remember: false,
}),
}
},
}
</script>
Vue 3
<template>
<form #submit.prevent="form.post('/login')">
<!-- email -->
<input type="text" v-model="form.email">
<div v-if="form.errors.email">{{ form.errors.email }}</div>
<!-- password -->
<input type="password" v-model="form.password">
<div v-if="form.errors.password">{{ form.errors.password }}</div>
<!-- remember me -->
<input type="checkbox" v-model="form.remember"> Remember Me
<!-- submit -->
<button type="submit" :disabled="form.processing">Login</button>
</form>
</template>
<script>
import { useForm } from '#inertiajs/inertia-vue3'
export default {
setup () {
const form = useForm({
email: null,
password: null,
remember: false,
})
return { form }
},
}
</script>
I solved the problem, it was the toRefs that was modifying the props in the component after the request was sent. Using a reactive object was the solution:
const eventRef = reactive(props.event)

VueJS - get input values from a recursive tree

I have the need to create a dynamic form, which builds up in a tree kind of way. The form can change at any time by the user (who creates/designs the form) and so the inputs I have change dynamically too.
Example:
root
--groeisnelheid
----niveau
------beginner (input radio)
------recreatief (input radio)
------competitie (input radio)
------tour (input radio)
----input text
----begeleiding
------another input
------and another
--another category
----speed
------input
------input
As you can see, not the easiest form... The user (admin user in this case) has the ability to edit or create new forms.
I probably have underestimated the job, since I am trying to create the input side of it, and am already struggling.
What I have done so far:
TreeComponent.vue
<template>
<div class="tree">
<ul class="tree-list">
<tree-node :node="treeData"></tree-node>
</ul>
</div>
</template>
<script>
export default {
props: {
treeData: [Object, Array]
},
data() {
return {
treeValues: []
};
},
methods: {
sendForm: function() {}
}
};
</script>
TreeNodeComponent.vue
<template>
<li v-if="node.children && node.children.length" class="node">
<span class="label">{{ node.name }}</span>
<ul>
<node v-for="child in node.children" :node="child" :key="child.id"></node>
</ul>
</li>
<div v-else class="form-check form-check-inline">
<input
type="radio"
class="form-check-input"
:name="'parent-' + node.parent_id"
:id="node.id"
/>
<label for class="form-check-label">{{ node.name }}</label>
</div>
</template>
<script>
export default {
name: "node",
props: {
node: [Object, Array]
}
};
</script>
This results in all the inputs showing up as I want. But now the real question is; how do I get the value of these inputs in my root component (TreeComponent.vue), so I can send this to the server. Either on change or when the user proceeds in the form.
I am used to working with v-model on this, but I have no clue on how to use this on recursive components, since the documentation only covers setting the data of the direct parent.
Any help would be much appreciated.
One way of doing this is to pass a prop down from TreeComponent to each node.
<template>
<div class="tree">
<ul class="tree-list">
<tree-node :node="treeData" :form="formRepo"></tree-node>
</ul>
</div>
</template>
Then each node passes the prop down to its children.
<node v-for="child in node.children" :node="child" :key="child.id" :form="form"></node>
This way each node will have a direct reference to the TreeComponent.
In each node you can watch the model and update the form prop. You need to use your child.id so you know which field is which.
Your formRepo can be a fully fledged object, but a hash could work just as well.
data() {
return {
treeValues: [],
formRepo: {
}
};
}
Note: if you want formRepo to be reactive you'll need to use Vue.set to add new keys to it.
Thank you for your answer.
I managed to solve it by posting the data to the server on every change, since this was a handy feature.
The way I did so was:
Call function from the input (same call for text inputs)
<input
type="radio"
class="form-check-input"
:name="'parent-' + node.parent_id"
:id="node.id"
#input="change($event, node.id, node.parent_id)"
/>
Have some data variables to fill (Route is required for the axios request)
data() {
return {
input: {
id: null,
parentId: null,
radio: false,
value: null
},
route: null
};
},
And then some magic. The change method. (Left the axios bit out.)
methods: {
change: function(event, id, parentId) {
this.input.parentId = parentId;
this.input.id = id;
if (event.target.value === "on" || event.target.value === "off") {
this.input.radio = true;
this.input.value = event.target.value === "on" ? true : false;
} else {
this.input.value = event.target.value;
}
if (this.input.value) {
axios.put().then().catch()
}
}
}
I know there is some room of improvement in the validation bit. If a user enters 'on' in a text field, this will probably fail. So there is work to be done, but the basic filling of a form is working.
As to if this is the best way, I have no clue, since I'm new to Vue.

submitting forms in vuejs, should I use the form tag?

I'm so confused how I should submit and handle edit forms in vuejs.
How I'm doing it now is I have a component called TreeForm.vue:
<template>
<div>
<v-text-field v-model="cloned_tree.root" />
<v-text-field v-model="cloned_tree.root" />
<v-file-input type="number" v-model="cloned_tree.fruits" />
<v-btn #click="$emit('save', {idx: tree_idx, tree: cloned_tree})">Save</v-btn>
</div>
</template>
<script>
export default {
props: {tree_idx: Number},
data() {
return {
cloned_tree: JSON.parse(JSON.stringify(this.$store.state.trees[this.tree_idx])),
};
},
};
</script>
And in the parent component I do:
<template>
<div>
...
<TreeForm tree_idx="0" #save="submitTreeForm" />
...
</div>
</template>
<script>
import {mapActions} from 'vuex';
export default {
methods: {
...mapActions(['submitTreeForm']),
},
};
</script>
And in my vuex I do:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.mydomain.com/api',
timeout: 10000,
withCredentials: true,
});
Vue.use(Vuex);
export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
state: {
trees: [
{
root: 'hello',
imageFile: require('some/picture'),
fruits: 5,
},
],
},
mutations: {
updateTree(state, payload) {
state.trees[payload.idx] = payload.tree;
},
},
actions: {
submitVideoForm({commit}, payload) {
api
.post('/trees/update/', payload)
.then(response => {
if (response.data.success == 1) {
commit('updateTree', payload);
} else {
console.log(response.data.success);
}
})
.catch(function(error) {
console.log(error);
});
},
},
});
But I feel like This is not the correct way to do it specially because I'm not using <v-form> or <form> tags. I also haven't incorporated validation yet, I'm thinking of using vuelidate. So please give me the best practice for submitting and handling edit form while validation is done by vuelidate.
Basically, the form tag is not mandatory. Unless using some kind of CSS framework, the UI will look the same with or without the form tag. But, it still has some pros:
Ben Nadel put it best in his blog post:
... it seems that using a FORM tag does have some benefits, depending
on your particular situation:
You need it if you want to execute a traditional (ie. non-AJAX) form
post.
You need it you want to capture the "submit" event
programmatically.
You need it in order for mobile Safari to show the
"Go" button on the keyboard.
You need it if you want to
programmatically "reset" a form (ie. call reset()).
It makes generic
form serialization easier (since it groups input fields).
You need it if you want to post files without the modern file API.
You need it if you have to segregate fields with the same name.
Vue.js gets you covered in almost all these situations. But if it doesn't - use form.

Categories