I used vue 2 quiet a lot and now finally trying to slowly using vue 3 but I am already kind of buffled of how this works. In vue 2, I used the created method to fetch API data and populate my data arrays. Now I was trying the vue 3 way by using onMounted and I can log the API response. What I dont seem to find out is, how can I make my streams array to be initialized by the json response from the API now?
<script setup lang="ts">
import { ref, onMounted } from "vue";
let streams = ref(); //should be array of Objects {marketId, timeframe}
onMounted(() => {
fetch("http://localhost:3000/stream")
.then((response) => response.json())
.then((result) => (streams.value = result)); //json response array {marketId, timeframe}, {marketId, timeframe}
console.log(streams.value); //returns undefined
});
</script>
Try to wait for response:
const { ref, onMounted } = Vue
const app = Vue.createApp({
setup() {
let streams = ref();
onMounted(async () => {
await fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((result) => (streams.value = result));
});
return {
streams,
};
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="stream in streams">
{{ stream }}
</div>
</div>
Related
The following code works and I can see the output as intended when use ref, but when using reactive, I see no changes in the DOM. If I console.log transaction, the data is there in both cases. Once transaction as a variable changes, should the changes not be reflected on the DOM in both cases?
I'm still trying to wrap my head around Vue 3's composition API and when to use ref and reactive. My understanding was that when dealing with objects, use reactive and use ref for primitive types.
Using ref it works:
<template>
{{ transaction }}
</template>
<script setup>
import { ref } from 'vue'
let transaction = ref({})
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction.value = res.data
})
.catch((e) => {
console.log(e)
})
}
getPayByLinkTransaction()
</script>
Using reactive it doesn't work:
<template>
{{ transaction }}
</template>
<script setup>
import { reactive } from 'vue'
let transaction = reactive({})
const getPayByLinkTransaction = () => {
axios({
method: "get",
url: "pay-by-link",
params: {
merchantUuid: import.meta.env.VITE_MERCHANT_UUID,
uuid: route.params.uuid,
},
})
.then((res) => {
transaction = { ...res.data }
})
.catch((e) => {
console.log(e)
})
}
getPayByLinkTransaction()
</script>
Oh, when you do transaction = { ...res.data } on the reactive object, you override it, like you would with any other variable reference.
What does work is assigning to the reactive object:
Object.assign(transaction, res.data)
Internally, the object is a Proxy which uses abstract getters and setters to trigger change events and map to the associated values. The setter can handle adding new properties.
A ref() on the other hand is not a Proxy, but it does the same thing with its .value getter and setter.
From what I understand, the idea of reactive() is not to make any individual object reactive, but rather to collect all your refs in one single reactive object (somewhat similar to the props object), while ref() is used for individual variables. In your case, that would mean to declare it as:
const refs = reactive({transaction: {}})
refs.transaction = { ...res.data }
The general recommendation seems to be to pick one and stick with it, and most people seem to prefer ref(). Ultimately it comes down to if you prefer the annoyance of having to write transaction.value in your script or always writing refs.transaction everywhere.
With transaction = { ...res.data } the variable transaction gets replaced with a new Object and loses reactivity.
You can omit it by changing the data sub-property directly or by using ref() instead of reactivity()
This works:
let transaction = ref({})
transaction.data = res.data;
Check the Reactivity in Depth and this great article on Medium Ref() vs Reactive() in Vue 3 to understand the details.
Playground
const { createApp, ref, reactive } = Vue;
const App = {
setup() {
const transaction1 = ref({});
let transaction2 = reactive({ data: {} });
const res = { data: { test: 'My Test Data'} };
const replace1 = () => {
transaction1.value = res.data;
}
const replace2 = () => {
transaction2.data = res.data;
}
const replace3 = () => {
transaction2.data = {};
}
return {transaction1, transaction2, replace1, replace2, replace3 }
}
}
const app = Vue.createApp(App);
app.mount('#app');
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app">
transaction1: {{ transaction1 }}
<button type="button" #click="replace1()">1</button>
<br/>
transaction2: {{ transaction2 }}
<button type="button" #click="replace2()">2</button>
<button type="button" #click="replace3()">3</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
Since reactive transaction is an object try to use Object.assign method as follows :
Object.assign(transaction, res.data)
<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}
In my template, in a v-slot (which means users is not available in <script setup>), I have
<template v-slot:body-cell-assignedTo="props">
<q-td :props="props">
<div v-for="u in props.users" :key="u">{{u}}</div>
</q-td>
</template>
This displays
john
mary
I can enrich this information by calling an API:
fetch('https://example.com/api/user/john')
.then(r => r.json())
.then(r => console.log(r))
This displays in the console John de Brown, 1262-1423.
My question: how to combine these two mechanisms? In other words, how to asynchronously update the value in {{}}?
I would need to do something like
<div v-for="u in props.users" :key="u">{{enrichFetchFunction(u)}}</div>
but it would need to be asynchronous, and yet somehow return a value.
EDIT: I will ultimately enrich the source data that is displayed in the v-slot. I would still be interested, though, if waiting for such an asynchronous function there (à la await) is doable in Vuie.
I assume you are using Compositions API. See this playground
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
onMounted(async() => {
fetch('https://mocki.io/v1/67abcfb6-4f25-4513-b0f9-1eb6c4906413')
.then(r => r.json())
.then(r => users.value = r)
})
</script>
<template>
<div v-for="u in users" :key="u">{{u}}</div>
</template>
This is doable with Lifecycle hooks such as mounted(), yet you will need some sort of listener to react to the information being changed. here is an example that updates the values as soon as it is mounted and includes a button that will also update the values (you can run the code here in Vue SFC Playground):
<template>
<div id="app">
<h1 v-for="u in enrichedUsers" :key="u">{{ u }}</h1>
<button #click="myAsyncFunction">
update
</button>
</div>
</template>
<script>
// pseudo api
const fetchEnrichedAPI = function(user) {
return new Promise( (resolve, reject) => {
var enrichedUsers = []
if (user.includes('john')) {
enrichedUsers.push('John de Brown, 1262-1423')
}
if (user.includes('mary')){
enrichedUsers.push('Mary de Purple, 1423-1262')
}
setTimeout(() => {
resolve(enrichedUsers);
}, 300);
});
}
export default{
data() {
return {
props : { users: ['john','mary'] },
enrichedUsers: []
}
},
mounted() {
// when mounted run this async function
this.myAsyncFunction()
},
methods: {
async myAsyncFunction() {
// call api passing the list of users
await fetchEnrichedAPI(this.props.users)
.then((data) => {
// if api work
this.enrichedUsers = data;
return true;
})
.catch((e) => {
// if the api doesn't work
console.error(e);
this.enrichedUsers = this.props.users;
})
}
},
}
</script>
I am aware that this does not use props, but it does work. If you would like to expand this to use props you may be able to do this with computed properties or functions in the v-for. See this post for more info on that.
I'm trying out Vue3 composition api and I'm having some issue writing tests for it.
I wrote new component(MyComponent) in my app using composition api. MyComponent uses another component that was written with Options api (MyOtherComponent).
Everything works fine if I run the app but when I write the Unit Test (using Jest) I start having issues where 'this' is not recognised anymore and evaluated as undefined.
Please see the code snippets below (take it as pseudo-code)...
Anyone knows how I can possibly fix or work around this issue?
MyOtherComponent.vue
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div></div>
<template>
<script lang="ts">
export default class MyOtherComponent extends Vue {
public doSomething() {
this.$log('MyOtherComponent is doing something!');
}
}
</script>
MyComponent.vue
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div #click="onClick">
<my-other-component ref="myOtherComponent" />
</div>
</template>
<script lang="ts">
export default {
name: 'MyComponent',
components: ['MyOtherComponent'],
setup() {
const myOtherComponent = ref<MyOtherComponent | null>(null);
const state = ref<Boolean>(false);
function onClick() {
myOtherComponent.value.doSomething().then(() => {
state.value = true;
});
}
return {
onClick
}
}
}
</script>
MyComponent.test.ts
fdescribe('Testing MyComponent', () => {
let wrapper: Wrapper<MyComponent>;
beforeEach(() => {
const localVue = createLocalVue();
localVue.use(VueCompositionApi);
wrapper = mount(MyComponent, { localVue };
})
afterEach(() => {
wrapper.destroy();
});
test('post click test', async() => {
expect(wrapper.vm.$data.state).toBeFalsy();
await wrapper.find('div:first-child').trigger('click');
expect(wrapper.vm.$data.state).toBeTruthy();
});
})
In Vue 3 there is no global Vue instance, so there's no need for createLocalVue.
Your beforeEach would change:
import { mount } from '#vue/test-utils';
// …
beforeEach(() => {
wrapper = mount(MyComponent);
});
// …
This is my script that calls axios and fetch data as posts
<script>
import axios from 'axios'
export default {
name: 'App',
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data)
},
data() {
return {
posts: null
}
},
};
</script>
My code on view that tries to fetch data as posts from the script above
<template>
<div id="app">
<ul>
<li v-for="post in posts" v-text="post.createdAt"></li>
</ul>
<div>
</template>
SAMPLE data fetched from API URL look like this
POSTS OBJECT VARIABLES
I am able to fetch API DATA in console log as an array but when I call one object from array which is createdAT, v-text = "post.createdAt" does not print/fetch list of createdAt date list.
Just solved it following this document USING AXIOS TO CONSUME API here is the link for that https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html .
Above code that I have posted works fine. Problem was on my API URL which was nested inside data[data[object]]. So the way I called data from that API
from this
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data)
}
to this
mounted: function () {
axios.get('API URL')
.then(response => this.posts = response.data.data)
}
posts isn't reactive because the default value is null, make it an empty array, or use Vue.set instead:
Array:
posts: []
Vue.set:
.then(response => Vue.set(this, 'posts', response.data))
Edit
In response to the comments below:
You must import Vue from 'vue' to resolve the Vue is not defined error.
Regarding your v-for-key needing to be unique, you need to define a unique key on v-for elements. You can just use JSON.stringify(post):
<li v-for="post in posts" v-text="post.createdAt" :key="JSON.stringify(post)"></li>