How do I change text every time I click the button? - vue.js

I'm clicking a button to make a call to an API to get a random fact and length. This is then displayed in two input fields which works. I'm struggling with how to change the text in the input fields to the new fact and length from the API when the button is clicked again. I know it is something simple but can't seem to find the solution. Any help would be appreciated.
<template>
<form>
<header>
<img alt="Vue logo" class="logo" src="./assets/logo.svg" width="300" height="300" />
<br>
<br>
<input type="text" id="idFact" v-model="facts">
<br>
<br>
<input type="text" id="idLength" v-model="lengths">
<br>
<br>
<button v-on:click="callAPI" type="button">Call</button>
</header>
</form>
</template>
<script>
export default {
data() {
return {
posts: '',
facts: '{fact}',
lengths: '{length}',
};
},
methods: {
async getData() {
try {
let response = await fetch("https://catfact.ninja/fact");
this.posts = await response.json();;
} catch (error) {
console.log(error);
}
},
callAPI() {
this.facts = this.posts.fact
this.lengths = this.posts.length
}
},
created() {
this.getData();
}
}
</script>
Thanks

Invoke the getData method inside the click handler callAPI, and remove the call from created hook in order to avoid redundant calls:
<script>
export default {
data() {
return {
posts: '',
facts: '{fact}',
lengths: '{length}',
};
},
methods: {
async getData() {
try {
let response = await fetch("https://catfact.ninja/fact");
this.posts = await response.json();;
} catch (error) {
console.log(error);
}
},
callAPI() {
this.getData()
this.facts = this.posts.fact
this.lengths = this.posts.length
}
},
}
</script>

Related

Vue 3 reusable error handling and handleSubmit in reusable 'useForm' function using the composition api

In a recent web app we have a lot of forms with the same submit structure:
Disable the form and submit button based on an isSubmitting variable
Validate the input fields (we're using Yup)
If validation fails: Set isSubmitting back to false + set and show validationErrors on the input fields
If validation succeed: Send post request with form data to api
Show general error if api is down or returns an error
I've tried to something using the composition api in vue 3.
Login.vue
<template>
<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h1 class="text-3xl text-center text-gray-900">{{ t('sign_in_account', 1) }}</h1>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<form #submit.prevent="handleSubmit">
<fieldset :disabled="isSubmitting" class="space-y-6">
<MessageBox v-if="errors.general" :title="errors.general" :messages="errors.messages" />
<Input :label="t('email', 1)" type="text" id="email" v-model="user.email" :error="errors.email" />
<Password :label="t('password', 1)" type="password" id="password" v-model="user.password" :error="errors.password" />
<div class="text-sm text-right">
<router-link class="font-medium text-indigo-600 hover:text-indigo-500" :to="forgotPassword">{{ t('forgot_password', 1) }}</router-link>
</div>
<SubmitButton class="w-full" :label="t('sign_in', 1)" :submittingLabel="t('sign_in_loader', 1)" :isSubmitting="isSubmitting" />
</fieldset>
</form>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import useForm from '#/use/useForm';
import { validateEmail, LoginValidationSchema } from '#/utils/validators';
export default {
setup() {
const store = useStore();
const { t } = useI18n({ useScope: 'global' });
const user = ref({
email: '',
password: '',
});
const { handleSubmit, isSubmitting, errors } = useForm(user, LoginValidationSchema, handleLogin);
async function handleLogin(values) {
try {
return await store.dispatch('auth/login', values);
} catch (error) {
if (error.response) {
console.log(error.reponse);
if (error.response.status == 422) {
errors.value = {
general: `${t('unable_to_login', 1)}<br /> ${t('fix_and_retry', 1)}`,
messages: Object.values(error.response.data.errors).flat(),
};
} else if (error.response.data.message) {
errors.value = {
general: error.response.data.message,
};
} else {
errors.value = {
general: `${t('unknown_error', 1)}<br /> ${t('please_try_agin', 1)}`,
};
}
} else if (error.request) {
console.log(error.request);
errors.value = {
general: `${t('unknown_error', 1)}<br /> ${t('please_try_agin', 1)}`,
};
} else {
errors.value = {
general: `${t('unknown_error', 1)}<br /> ${t('please_try_agin', 1)}`,
};
}
return;
}
}
return { t, user, handleSubmit, isSubmitting, errors };
},
computed: {
forgotPassword() {
return validateEmail(this.user.email) ? { name: 'forgotPassword', query: { email: this.user.email } } : { name: 'forgotPassword' };
},
},
};
</script>
useForm.js
import { ref, watch } from 'vue';
export default function useForm(initialValues, validationSchema, callback) {
let values = ref(initialValues);
let isSubmitting = ref(false);
let errors = ref({});
async function handleSubmit() {
try {
errors.value = {};
await validationSchema.validate(values.value, { abortEarly: false });
isSubmitting.value = true;
} catch (err) {
console.log('In the catch');
isSubmitting.value = false;
err.inner.forEach((error) => {
errors.value = { ...errors.value, [error.path]: error.message };
});
}
}
watch(isSubmitting, () => {
if (Object.keys(errors.value).length === 0 && isSubmitting.value) {
callback(values);
isSubmitting.value = false;
} else {
isSubmitting.value = false;
}
});
return { handleSubmit, isSubmitting, errors };
}
This is somehow working but I'm missing two things. In useForm I want to wait till the callback is done (succeed or failed) to set isSubmitting back to false. Is a promise a good way to do this of is there a better way? Secondly I want a reusable way to handle the errors in Login.vue. Any suggestion how to handle this?
Regarding your first question - try..catch statements have a third statement called finally which always executes after the try statement block has completed.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
To answer your second question - promises are a great way of handling async logic, including your case when the API you're sending the request to returns an error response, and you can then decide how you're going to handle the UX in such scenario.
I'm not quite clear on what you mean by handle the errors in Login.vue in a reusable way, but perhaps you could simple pass in an empty array prop to your useForm called formErrors and have your useForm.js emit an update:modelValue event to get two way binding.

data in Vue instance doesn't get updated after axios post response

I am writing a code piece to submit the html form data on a POST REST API. Using Vue.js and axios for that.
My Vue.js code is like this -
const app = new Vue({
el: "#main-div",
data() { return {
name: 'Please enter the name',
showEdit: true,
showResponse: true,
responseText: null
}
},
methods: {
savePerson: function () {
this.showEdit = false;
axios
.post('/api/person', {
name: this.name
})
.then(function (response) {
this.responseText = response.data.name+ ' added successfully.';
console.log(response);
console.log(response.data.name+ ' added successfully.');
})
.catch(function (error) {
this.responseText = error.message;
console.log(error);
});
}
}
}
)
And html -
<div id="main-div">
<h2> Fill out the details to create a Person</h2>
<div v-if="showEdit">
<form >
<div>
Name: <input v-bind:value = 'name' type="text" v-on:focus="name= ''" />
</div>
<div>
<button v-on:click="savePerson">Save</button>
</div>
</form>
</div>
<div v-if="showResponse">
<div><p>{{ responseText }}</p></div>
<div>
<button v-on:click="showEdit = true">Add one more person</button>
</div>
</div>
This code doesn't update responseText. That I can check in Vue plugin in browser.
Any idea what is not correct in my example?
You need to use an arrow function in the callback or else the function injects its own this context:
.then((response) => {
...
})
.catch((error) => {
...
})
Or you could use async/await:
async savePerson() {
this.showEdit = false;
try {
const response = await axios.post('/api/person', {
name: this.name
})
this.responseText = response.data.name+ ' added successfully.';
} catch(error) {
this.responseText = error.message;
}
}
to bind data with the input field you need to use v-model in the HTML and try to use the arrow function in the API call.

Vue.js Send an index with #input event

Vue version : 3.1.1
Hey guys,
I'm working with dynamic Creation Component, which means a user can add whatever of component he wants.I create it base on this documentation dynamic component creation.
And I use this component vue image uploader.
I need to send an index when the user wants to upload the image, like this :
<div v-for="(line, index) in lines" v-bind:key="index">
{{index}}//if i log the index its 0,1,2,3 and its ok
...
<image-uploader
:preview="true"
:class-name="['fileinput', { 'fileinput--loaded': line.hasImage }]"
:capture="false"
:debug="0"
:auto-rotate="true"
output-format="blob"
accept="image/*"
#input="setImage(output , index)"
:ref="'fileUpload'+index"
>
...
And the setImage funciton :
setImage: function(output,index) {
console.log(index);
console.log(output);
return ;
this.lines[index].hasImage = true;
this.lines[index].image = output;
let formData = new FormData();
formData.append("file", output);
Ax.post(upload_route, formData, {
headers: { "Content-Type": "multipart/form-data" }
})
.then(response => {
// upload successful
})
.catch(error => console.log(error));
}
And the log result is:
The index always is 0 :(
How can i send an index when i want to upload it?
I read this passing event and index and test it but it's not working on component.
Because This is a custom event not a DOM event.
what should I do?
thanks.
Because you're actually passing the return value of setImage to the #input, not the method.
You can't just add extra parameters to setImage, as ImageUploader component just emit an image to the setImage. If you need to add extra parameters to that method, you need to create custom element that wrap ImageUploader.
It's something like this:
ImageUpload.vue
<template>
<image-uploader
:debug="0"
:autoRotate="true"
outputFormat="blob"
:preview="true"
:className="['fileinput', { 'fileinput--loaded' : hasImage }]"
:capture="false"
accept="image/*"
doNotResize="['gif', 'svg']"
#input="setImage"
v-on="listeners" />
</template>
<script>
export default {
props: {
index: {
required: true,
type: Number
}
},
data() {
return {
hasImage: false,
image: null
};
},
computed: {
listeners() {
const listeners = { ...this.$listeners };
const customs = ["input"];
customs.forEach(name => {
if (listeners.hasOwnProperty(name)) {
delete listeners[name];
}
});
return listeners;
}
},
methods: {
setImage(image) {
this.hasImage = true;
this.image = image;
this.$emit("input", this.index, image); // here, we emit two params, as index for the first argument, and the image at the second argument
}
}
};
</script>
Then, you can use that component something like this:
<template>
<div class="container">
<div v-for="(line, index) in lines" :key="index">
<image-upload :index="index" #input="setImage"/>
</div>
</div>
</template>
<script>
import ImageUpload from "./ImageUpload";
export default {
components: {
ImageUpload
},
data() {
return {
lines: ["1", "2", "3", "4"]
};
},
methods: {
setImage(index, image) {
console.log("Result", index, image);
}
}
};
</script>
See the working example: https://codesandbox.io/s/vue-template-ccn0e
Just use $event like this...
#input="setImage($event, index)"
...and you're done!

Vue.js / How to access the function in another component

I just started learning Vue.js. I need to call the function in another component in my project. When I add new data to the table with createAnnouncement.vue, I want to go into announcement.vue and call the queryAnnouncement function. How can I do that? I would appreciate if you could help, please explain with a sample. Or edit my codes.
Announcement.Vue template:
<template>
// more div or not important template code
<div class="dataTables_filter" style="margin-bottom:10px">
<label>
<input class="form-control form-control-sm" placeholder="Search" aria-controls="m_table_1" type="search" v-model="searchField" #change="filter()">
</label>
<a style="float:right" href="#" data-target="#create-announcement-modal" data-toggle="modal" class="btn btn-primary">
<i class="">Add</i>
</a>
</div>
// more div or not important template code
</template>
Announcement.Vue Script Code:
<script>
import toastr from "toastr";
export default {
name: 'announcement',
data() {
return {
announcements: [],
searchField: "",
deleteCsrfToken: this.$root.csrfTokens["deleteAnnouncement"]
}
},
beforeMount: async function () {
await this.queryAnnouncements();
},
methods: {
filter: async function () {
await this.queryAnnouncements(this.searchField);
},
queryAnnouncements: async function (filter, pageSize, pageIndex, sortBy, sortType) {
var data = {
"query[general-filter]": filter,
"pagination[perpage]": !!pageSize ? pageSize : 10,
"pagination[page]": !!pageIndex ? pageIndex : 1,
"sort[field]": sortType,
"sort[sort]": !!sortBy ? sortBy : "asc"
};
let response = await axios
.get("/Announcement/QueryAnnouncements", { params: data })
this.announcements = response.data.data;
},
}
}
createAnnouncement.vue code:
<template>
<button #click="createAnnouncement()" v-bind:disabled="contentDetail === ''" class="btn btn-success">
Save</button>
//not important template codes
<template>
<script>
import toastr from "toastr";
export default {
name: 'create-announcement',
data() {
return {
contentDetail: "",
createCsrfToken: this.$root.csrfTokens["createAnnouncement"],
}
},
methods: {
createAnnouncement: async function () {
var self = this;
var data = {
content: this.contentDetail,
};
let response = await axios
.post("/Announcement/createAnnouncement",
data,
{
headers: {
RequestVerificationToken: self.createCsrfToken
}
})
if (response.status == 200) {
$("#create-announcement-modal .close").click()
$("#create-announcement-form").trigger('reset');
toastr["success"]("Kayıt başarıyla eklendi!", "Başarılı!");
self.contentDetail = "";
}
else {
toastr["warning"]("Hata", "Kayıt Eklenemedi.");
}
}
},
}
</script>
Please show with sample or arrangement, my english is not very good. Thanks.

VueJS Trying to capture enter press on child component before parent component handles enter

I have an auto complete input that when I press enter in this input, it is to emit its value to the parent component and then the parent submit action should be handled. However it appears that the parent is first receiving the enter key submiting the form and then the child component will finally emit the value meaning the data doesnt get updated until after it is needed.
I have an example code pen I made up
codepen
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #keyup.enter.capture="enterPressed" v-model="someData" />
</div>
`,
methods: {
enterPressed(){
this.$emit('updateData',this.someData)
console.log('CHILD: enter pressed')
}
}
});
Vue.component('parent', {
data () {
return {
lastGo: null,
parentData: "init"
}
},
template: `
<form v-on:submit.prevent="go">
<child #updateData="updateData"></child>
<button #click="go">Go</button>
<p>Parent data: <b>{{parentData}}</b></p>
<p>Last go: <b>{{lastGo}}</b></p>
</form>
`,
methods: {
updateData(data){
this.parentData = data;
},
go(){
this.lastGo = this.parentData;
console.log("go: "+this.parentData)
}
}
});
new Vue({
el: '#app'
});
I'm not sure how to resolve this, I feel maybe that my pattern just isn't going to work, is there a better way?
There is a way to work around is using #input event in child component
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #input="onInput" v-model="someData" />
</div>
`,
methods: {
onInput(){
console.log('CHILD: enter pressed')
this.$emit('updateData',this.someData)
}
}
});
Demo