Passing an axios response to the template in Vue 3 & Composition API - vue.js

<template>
<div class="home">
<h1>BPMN Lint Analyzer</h1>
<!-- Get File from DropZone -->
<DropZone #drop.prevent="drop" #change="selectedFile"/>
<span class="file-info">File:{{dropzoneFile.name}}</span>
<button #click="sendFile" >Upload File</button>
<!-- Display Response Data (Not Working)-->
<div v-if="showResponseData">
<p>Testing: {{responseData}}</p>
</div>
</div>
</template>
<script>
import DropZone from '#/components/DropZone.vue'
import {ref} from "vue"
import axios from 'axios'
export default {
name: 'HomeView',
components: {
DropZone
},
setup(){
let dropzoneFile = ref("")
//Define Response variable and visibility toggle
var responseData=''
// var showResponseData = false
//Methods
const drop = (e) => {
dropzoneFile.value = e.dataTransfer.files[0]
}
const selectedFile = () => {
dropzoneFile.value = document.querySelector('.dropzoneFile').files[0]
}
//API Call
const sendFile = () => {
let formData = new FormData()
formData.append('file', dropzoneFile.value)
axios.post('http://localhost:3000/fileupload', formData,{
headers: {
'Content-Type':'multipart/form-data'
}
}).catch(error => {
console.log(error)
}).then(response => {
responseData = response.data
console.log(responseData);
})
// showResponseData=true
}
return{dropzoneFile, drop, selectedFile, sendFile}
}
}
</script>
I'm trying to pass the response from sendFile, which is stored in responseData back to the template to display it in a div to begin with. I'm not sure if a lifecycle hook is needed.
Current output:
I played around with toggles, I tried to convert everything to options API. Tried adding logs but I'm still struggling to understand what I'm looking for.
Unfortunately I am stuck with the Composition API in this case even if the application itself is very simple. I'm struggling to learn much from the Docs so I'm hoping to find a solution here. Thank you!

You need to make responseData reactive, so try to import ref or reactive from vue:
import {ref} from 'vue'
then create your variable as a reactive:
const responseData = ref(null)
set data to your variable:
responseData.value = response.data
in template check data:
<div v-if="responseData">
<p>Testing: {{responseData}}</p>
</div>
finally return it from setup function (if you want to use it in template):
return{dropzoneFile, drop, selectedFile, sendFile, responseData}

Related

The data from Pinia store is not reactive in Nuxt 3 when switching language

I'm just starting with Nuxt and the answer could be obvious, but I'm hoping to get support from you.
I've got a 2 language website, built with Nuxt 3 that uses Nuxt I18n for internationalization, which retrieves data from an API (a strapi headless cms). I've managed to set up a Pinia store in order to not overuse the API, which looks like this:
// /stores/store.js
import { defineStore } from "pinia";
import { useFetch } from "#app";
export const useStore = defineStore("store", {
state: () => ({
data: {
en: [],
ru: []
}
}),
actions: {
async fetchData() {
let resEn = await useFetch('strapi-url.com/api/data', {
params: {
locale: 'en'
}
});
if (resEn.error.value) {
throw createError({
statusCode: resEn.error.value.statusCode,
statusMessage: resEn.error.value.statusMessage
});
}
this.data.en = resEn.data;
let resFr = await useFetch('strapi-url.com/api/data', {
params: {
locale: 'fr'
}
});
if (resFr.error.value) {
throw createError({
statusCode: resFr.error.value.statusCode,
statusMessage: resFr.error.value.statusMessage
});
}
this.data.fr = resFr.data;
}
}
});
And to make the data available when app loads I've setup the app.vue file:
<script setup>
import { useStore } from "~/stores/store";
const store = usetStore();
await store.fetchData();
</script>
<template>
<div>
<Header/>
<NuxtPage/>
<Footer/>
</div>
</template>
and then in a component (ex: Header.vue) I'm getting the data from the store an render it:
<script setup>
import { useStore } from "~/stores/NewsletterStore";
import { storeToRefs } from "pinia";
const { locale } = useI18n();
const store = useStore();
const { data } = storeToRefs(store);
const title = data[locale].title;
</script>
<template>
<div>
{{ title }}
</div>
</template>
The problem is that when the language changes, by a locale switcher, the data isn't refreshed, even if the locale changes too.
I would like to know if there's any way to make it reactive, based on the selected locale.
Thanks & looking forward.
I've tried to setup a pinia store using nuxt 3 web app that has 2 languages controlled by Nuxt I18n module that consumes data from an strapi backend API, but the data rendered isn't reactive when changing locale. I expect to know how to make this data be reactive, when language changes?

How to use vue 3 suspense component with a composable correctly?

I am using Vue-3 and Vite in my project. in one of my view pages called Articles.vue I used suspense component to show loading message until the data was prepared. Here is the code of Articles.vue:
<template>
<div class="container">
<div class="row">
<div>
Articles menu here
</div>
</div>
<!-- showing articles preview -->
<div id="parentCard" class="row">
<div v-if="error">
{{ error }}
</div>
<div v-else>
<suspense>
<template #default>
<section v-for="item in articleArr" :key="item.id" class="col-md-4">
<ArticlePrev :articleInfo = "item"></ArticlePrev>
</section>
</template>
<template #fallback>
<div>Loading...</div>
</template>
</suspense>
</div>
</div> <!-- end of .row div -->
</div>
</template>
<script>
import DataRelated from '../composables/DataRelated.js'
import ArticlePrev from "../components/ArticlePrev.vue";
import { onErrorCaptured, ref } from "vue";
/* start export part */
export default {
components: {
ArticlePrev
},
setup (props) {
const error = ref(null);
onErrorCaptured(e => {
error.value = e
});
const {
articleArr
} = DataRelated("src/assets/jsonData/articlesInfo.json");
return {
articleArr,
error
}
}
} // end of export
</script>
<style scoped src="../assets/css/viewStyles/article.css"></style>
As you could see I used a composable js file called DataRelated.js in my page that is responsible for getting data (here from a json file). This is the code of that composable:
/* this is a javascript file that we could use in any vue component with the help of vue composition API */
import { ref } from 'vue'
export default function wholeFunc(urlData) {
const articleArr = ref([]);
const address = urlData;
const getData = async (address) => {
const resp = await fetch(frontHost + address);
const data = await resp.json();
articleArr.value = data;
}
setTimeout(() => {
getData(address);
}, 2000);
return {
articleArr
}
} // end of export default
Because I am working on local-host, I used JavaScript setTimeout() method to delay the request to see that the loading message is shown or not. But unfortunately I think that the suspense component does not understand the logic of my code, because the data is shown after 2000ms and no message is shown until that time. Could anyone please help me that what is wrong in my code that does not work with suspense component?
It's a good practice to expose a promise so it could be chained. It's essential here, otherwise you'd need to re-create a promise by watching on articleArr state.
Don't use setTimeout outside the promise, if you need to make it longer, delay the promise itself.
It could be:
const getData = async (address) => {
await new Promise(resolve => setTimeout(resolve, 2000);
const resp = await fetch(frontHost + address);
const data = await resp.json();
articleArr.value = data;
}
const promise = getData(urlData)
return {
articleArr,
promise
}
Then:
async setup (props) {
...
const { articleArr, promise } = DataRelated(...);
await promise
...
If DataRelated is supposed to be used exclusively with suspense like that, it won't benefit from being a composable, a more straightforward way would be is to expose getData instead and make it return a promise of the result.

Making API call using Axios with the value from input, Vue.js 3

I am making an app using this API. The point I'm stuck with is calling the API. If I give the name of the country, the data of that country comes.
Like, res.data.Turkey.All
I want to get the value with input and bring the data of the country whose name is entered.
I am getting value with searchedCountry. But I can't use this value. My API call does not happen with the value I get. I'm getting Undefined feedback from Console.
Is there a way to make a call with the data received from the input?
<template>
<div>
<input
type="search"
v-model="searchedCountry"
placeholder="Search country"
/>
</div>
</template>
<script>
import axios from 'axios';
import { ref, onMounted} from 'vue';
export default {
setup() {
let data = ref([]);
const search = ref();
let searchedCountry = ref('');
onMounted(() => {
axios.get('https://covid-api.mmediagroup.fr/v1/cases').then((res) => {
data.value = res.data.Turkey.All;
});
});
return {
data,
search,
searchedCountry,
};
},
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I'm work with Vue.js 3
There are a few things wrong with your code:
Your axios call is only called once, when the component mounts (side note here, if you really want to do something like that, you can do it directly within the setup method)
You don't pass the value from searchedCountry to the axios API
Use const for refs
I'd use a watch on the searchedCountry; something like this (I don't know the API contract):
<template>
<div>
<input
type="search"
v-model="searchedCountry"
placeholder="Search country"
/>
</div>
</template>
<script>
import axios from 'axios';
import { ref, watch } from 'vue';
export default {
setup() {
const searchedCountry = ref('');
const data = ref([]);
watch(
() => searchedCountry,
(country) => axios.get(`https://covid-api.mmediagroup.fr/v1/cases/${country}`).then((res) => data.value = res.data.Turkey.All);
);
return {
data,
searchedCountry,
};
},
};
</script>

Is vue2-dropzone compatible with vue3?

vue2-dropzone is working fine for vue2 but not working for vue3.
With the following code
import vue2Dropzone from 'vue2-dropzone'
import 'vue2-dropzone/dist/vue2Dropzone.min.css'
return {
dropzoneOptions: {
autoProcessQueue: false,
addRemoveLinks: true,
url: this.url,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
},
id: null,
myDropZone: null,
supervisorError: ''
}
}
I do have the following error
TypeError: Cannot read property '_c' of undefined vue3
vue3-dropzone
What you are after is vue3-dropzone.
It worked highly similar to the vue2-dropzone package that most of you may have been using with vue2. I myself am one of the contributors to the new vue3-dropzone package. I have just added the example code for those who want to Save Multiple Files at once, as shown below:
Example of Saving Multiple Files
<template>
<div>
<div v-bind="getRootProps()">
<input v-bind="getInputProps()" />
<p v-if="isDragActive">Drop the files here ...</p>
<p v-else>Drag 'n' drop some files here, or click to select files</p>
</div>
<button #click="open">open</button>
</div>
</template>
<script>
import { useDropzone } from "vue3-dropzone";
import axios from "axios";
export default {
name: "UseDropzoneDemo",
setup() {
const url = "{your_url}"; // Your url on the server side
const saveFiles = (files) => {
const formData = new FormData(); // pass data as a form
for (var x = 0; x < files.length; x++) {
// append files as array to the form, feel free to change the array name
formData.append("images[]", files[x]);
}
// post the formData to your backend where storage is processed. In the backend, you will need to loop through the array and save each file through the loop.
axios
.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.info(response.data);
})
.catch((err) => {
console.error(err);
});
};
function onDrop(acceptFiles, rejectReasons) {
saveFiles(acceptFiles); // saveFiles as callback
console.log(rejectReasons);
}
const { getRootProps, getInputProps, ...rest } = useDropzone({ onDrop });
return {
getRootProps,
getInputProps,
...rest,
};
},
};
</script>
As stated in this post: https://github.com/rowanwins/vue-dropzone/issues/578
It looks like vue-dropzone does not support Vue3 as of right now, I mean the mantainer was already struggling to manage the vue 2 and asked for help so it seems legit.
Maybe give a look to this vue3 one: https://github.com/Yaxian/vue3-dropzone
Here is a list of available alternatives: https://github.com/vuejs/awesome-vue#drag-and-drop
well, we're using this package for our production builds:
Vue3 Library Component for drag’n’drop file uploads with image previews.
https://github.com/darknessnerd/drop-zone

VueJS: TypeError: Cannot read property of undefined when Reload

I have a page like this:
<template>
<div class="row flex">
{{posts.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
async asyncData ({ route }) {
let { data } = await axios.get('http://localhost:8000/api/v1/feeds/' + route.params.id + '/')
return {
posts: data
}
}
}
</script>
When I click link with hot reload (router-link), it display well. But when I reload this window, it appear in 1 seconds and disappear then.
Video: http://g.recordit.co/ht0a0K2X81.gif
Error Log:
How can I fix this?
Add a property to your data i.e dataLoaded: false. When your ajax request has finished, set this.dataLoaded = true. On your template add v-if="dataLoaded. This will mean the template data won't render until you're ready.
You could also do v-if="posts" as another way but I generally have a consistent dataLoaded prop available to do this.
Edit: I just looked at your example again and doing something like this would work:
<template>
<div class="row flex" v-if="posts">
{{posts.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
posts: null
}
}
methods:{
loadPosts () {
return axios.get('http://localhost:8000/api/v1/feeds/' + this.$route.params.id + '/')
}
},
created () {
this.loadPosts().then(({data}) => {
this.posts = data
})
}
}
</script>
I've removed the async and just setting posts when the axios request returns it's promise. Then on the template, it's only showing posts is valid.
Edit
You can also use your original code and just add v-if="posts" to the div you have in your template.