Progress bar with Vue and Axios object - vue.js

I would like to create an upload progress bar with Axios.
Everything is working fine with server sending and response.
The problem is that I don't know how to capture the progress percentage (which is correctly calculated) from my exported object.
My file upload.js:
import axios from 'axios'
const baseUrl = 'http://localhost:80/upload.php'
const config = {
Headers: {'Content-Type': 'multipart/form-data'},
onUploadProgress: progressEvent => {
return parseInt(Math.round((progressEvent.loaded / progressEvent.total) * 100))
}
}
export default {
send (data) {
return axios.post(baseUrl, data, config)
}
}
My Vue component:
<template>
<div>
<label>File:</label>
<input type="file" id="file" ref="file" #change="changeFile()" />
<button #click="submit()">Upload</button>
<br />
<progress max="100" :value.prop="uploadPercentage"></progress>
</div>
</template>
<script>
import upload from '../services/upload.js'
export default {
name: 'Upload',
data: () => ({
file: null,
uploadPercentage: 0
}),
methods: {
submit () {
const formData = new FormData()
formData.append('file', this.file)
upload.send(formData)
.then(res => {
console.log(res.data)
})
.catch(() => {
console.log('Failure')
})
},
changeFile () {
this.file = this.$refs.file.files[0]
}
}
}
</script>
How to retreive, from the component submit method, the info sent by the onUploadProgress in order to update the data uploadPercentage?
Thanks.
Regards.

You need to pass a function to your send operation that will be called later.
See example below
const config = {
Headers: {'Content-Type': 'multipart/form-data'},
onUploadProgress: progressEvent => {
var progress= parseInt(Math.round((progressEvent.loaded / progressEvent.total) * 100));
if (config.onProgress)
config.onProgress(progress);
}
}
export default {
send (data, onProgress) {
config.onProgress= onProgress;
return axios.post(baseUrl, data, config)
}
}
Then you upload code will be
upload.send(formData,(pogress)=>{
// Update your uploadPercentage here
})
.then(res => {
console.log(res.data)
})
.catch(() => {
console.log('Failure')
})

Related

watchEffect does not run when inserting new data into a list

I am making an application which the user can create projects and assign to a user, for that I am consuming an api made in go, the form sends the data to an api to persist the information and then the api returns the input data to me record the data in the array.
My form in Vue 3:
export default {
setup() {
const name = ref('');
const description = ref('');
const endProject = ref('');
const user = ref(0);
const users = ref();
const listProjects = inject('listProjects');
const router = useRouter();
fetch(`http://localhost:8000/api/v1/users/`)
.then((data) => data.json())
.catch((error) => console.error(error))
.then((response) => {
users.value = response;
});
const saveProject = async () => {
if (
name.value === '' ||
description.value === '' ||
endProject.value === '' ||
user.value === ''
) {
console.log('error');
return;
}
await fetch(`http://localhost:8000/api/v1/projects`, {
method: 'POST',
body: JSON.stringify({
name: name.value,
description: description.value,
end_date: new Date(endProject.value.replace('-', '/')),
user: {
id_user: user.value,
},
}),
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.catch((error) => console.error('error:', error))
.then((data) => {
listProjects.value.push(data);
});
router.push({
name: 'Project',
});
};
return {
name,
description,
endProject,
user,
users,
saveProject,
};
},
};
</script>
The problem happens is when I want to update the api which brings all the projects, I consider that the error occurs because the api where it registers and brings all the information are in different views, one in a form and the other in the view principal.
This is my App.vue
<script>
import { ref } from '#vue/reactivity';
import Navbar from './components/layout/Navbar.vue';
import Sidebar from './components/layout/Sidebar.vue';
import { provide, watchEffect } from '#vue/runtime-core';
export default {
components: { Navbar, Sidebar },
setup() {
const listProjects = ref();
watchEffect(() => {
console.log('bdfbdfb');
fetch(`http://localhost:8000/api/v1/projects`)
.then((res) => res.json())
.catch((err) => console.log(err))
.then((response) => {
listProjects.value = response;
});
});
provide('listProjects', listProjects);
return {
listProjects,
};
},
};
</script>
From comments:
I am expecting that every time the view where the array is pushed is done, App.vue executes the watchEffect
Well it is not how watchEffect works. watchEffect is designed to execute some "side effects" (like fetch call) that depend on some reactive data (for example URL for fetch is created using some ID ref). Yours watchEffect does not depend on any reactive data. It only assign new value to a listProjects ref. (in other words, result of the function you pass to watchEffect does not depend on listProjects ref value at all)
You can debug watchEffect (and watch) using onTrack option. See example bellow. 1st watchEffect does not trigger onTrack because it does not read any reactive data. 2nd does.
const app = Vue.createApp({
setup() {
const projects = Vue.ref([])
Vue.watchEffect(() => {
projects.value = []
}, {
onTrack(e) {
console.log('watchEffect 1 is tracking projects')
}
})
Vue.watchEffect(() => {
console.log(projects.value)
}, {
onTrack(e) {
console.log('watchEffect 2 is tracking projects')
}
})
}
}).mount('#app')
<script src="https://unpkg.com/vue#3.1.5/dist/vue.global.js"></script>
<div id='app'>
</div>

How to fetch data in Vue 3?

I don't know how to fetch data with Vue 3? I created one action and with this action I am calling endpoint (https://api.openbrewerydb.org/breweries/5494). I didn't get response data.
Endpoint:
import { createStore } from 'vuex'
export default createStore({
state: {
},
mutations: {
},
actions: {
async getData() {
await fetch('https://api.openbrewerydb.org/breweries/5494', {
method: 'get',
headers: { 'Content-type': 'application/json' },
}).then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
console.log('response: ', response)
}).catch((error) => {
console.log('Looks like there was a problem: \n', error);
});
}
},
modules: {
}
})
Vue component:
<template>
<div #click="loadData">Load Data</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
name: 'HelloWorld',
props: {
msg: String
},
setup () {
const store = useStore()
const loadData = () => {
store.dispatch('getData')
}
return { loadData }
}
}
</script>
As a response I didn't get anything but I should get:
{"id":5494,"name":"MadTree Brewing","brewery_type":"regional","street":"3301 Madison Rd","address_2":null,"address_3":null,"city":"Cincinnati","state":"Ohio","county_province":null,"postal_code":"45209-1132","country":"United States","longitude":"-84.4239715","latitude":"39.1563725","phone":"5138368733","website_url":"http://www.madtreebrewing.com","updated_at":"2018-08-24T15:44:22.281Z","created_at":"2018-07-24T01:34:01.620Z"}
You need to make the data to json
.then(res=>res.json())
this will do the trick for you.
const getData = () => {
fetch('https://api.openbrewerydb.org/breweries/5494', {
headers: { 'Content-type': 'application/json' },
}).then(res=>res.json()).then((response) => {
console.log({ response })
}).catch((error) => {
console.log('Looks like there was a problem: \n', error);
});
}
getData();
If the response fails, it will surely get you to catch.
This answer Should be the accepted answer.
If readers landed here while working through the introductory examples on the Vue.js website, Adarsh's code can be adapted thusly (for Vue.js 3):
<div id="beer">
{{ message }}
</div>
const Breweries = {
data() {
return {
message: ""
}},
mounted() {
fetch('https://api.openbrewerydb.org/breweries/', {
headers: { 'Content-type': 'application/json' },
}).then(res=>res.json()).then((response) => {
this.message = response;
}).catch( (error) => {
this.message = error;
});
}
}
Vue.createApp(Breweries).mount('#beer')
First you must install a package like axios
Then create an object from axios and call the API
import axios from "axios";
export default {
setup() {
function getUsers() {
axios.get("https://jsonplaceholder.typicode.com/users")
.then(function (response) {
// handle success
console.log(response.data);
})
.catch(function (error) {
// handle error
console.log(error);
});
getUsers();
}
return { getUsers };
},
};

Axios/Vue/Nuxt - Find out when all API calls are finished

I want to make several API calls to get data into a component. I created a PostService.ts that looks like this:
const apiClient = axios.create({
baseURL: '/api/v1',
})
export default {
async getPosts() {
const { data }: { data: Post[] } = await apiClient.get('/posts')
// transform data ...
return data
},
async getTags() {
const { data }: { data: Tag[] } = await apiClient.get('/tags')
return data
},
async getComments() {
const { data }: { data: Comment[] } = await apiClient.get('/comments')
return data
},
}
This is my posts.vue:
<template>
<div>
<div v-if="dataLoaded">
content
</div>
<div v-else>
loading...
</div>
</div>
</template>
<script>
finishedApiCalls = 0
get dataLoaded() {
return this.finishedApiCalls === 3
}
created() {
PostService.getPosts()
.then((posts) => {
this.posts = posts
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getTags()
.then((tags) => {
this.tags = tags
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
PostService.getComments()
.then((comments) => {
this.comments = comments
this.finishedApiCalls++
})
.catch((error) => {
console.log('There was an error:', error)
})
}
</script>
The key point is that I want to display a loading spinner as long as the data has not been loaded. Is it recommended to make the API calls from created()? What would be a more elegant way to find out when all calls are finished? It does not feel right to use the finishedApiCalls variable.
I recommend using Nuxt's fetch method along with Promise.all() on all your async PostService fetches:
// MyComponent.vue
export default {
fetch() {
return Promise.all([
PostService.getPosts().then((posts) => ...).catch((error) => ...),
PostService.getTags().then((tags) => ...).catch((error) => ...),
PostService.getComments().then((comments) => ...).catch((error) => ...)
])
}
}
Nuxt provides a $fetchState.pending prop that you could use for conditionally rendering a loader:
<template>
<div>
<Loading v-if="$fetchState.pending" />
<div v-else>My component data<div>
</div>
</template>
You can use Promise.all for this kind of requirements.
this.loading = true
Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
.then(values => {
let [posts, tags, comments] = values
this.posts = posts
this.tags = tags
this.comments = comments
//Here you can toggle your fetching flag like below
this.loading = false
})
You can use Promise.all(). This will wait till all resolves or if 1 fails.
With async / await you can make it "synchronous"
data() {
return {
loaded: false
}
},
async created() {
let [posts, tags, comments] = await Promise.all([PostService.getPosts(), PostService.getTags(), PostService.getComments()])
this.posts = posts;
this.tags = tags;
this.comments = comments;
this.loaded = true;
}

How to use "Image-Compressor" this plugin in my image uploading code?

I have created this file using antd upload component. I want that when I upload the image, it should compress the size and quality max size is 2mb and send to backend. I have found the plugin, but I don't know how to use it. Can you help me to solve this problem?
Here is my code:
I have created the class component:
class Upload extends React.Component {
state = {
fileList: []
};
handleImageUpload = ({ onSuccess, onError, file }) => {
let formData = new FormData();
formData.set("documentId", "");
formData.append("image", file);
console.log(base_url);
axios .post(`${base_url}/uploadImage`, formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(res => {
console.log(res);
onSuccess();
this.props.form.setFieldValue(this.props.field.name, res.data);
})
.catch(err => {
console.log(err);
onError();
});
};
handleChange = ({ fileList }) => this.setState({ fileList });
render() {
const { fileList } = this.state;
const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text" style={{ color: "black" }}>
Upload
</div>
</div>
);
const uploadedImage = (
<img
src={`${base_url}/viewImage`}
alt=""
/>
);
return (
<div className="clearfix">
<MyUpload
customRequest={this.handleImageUpload}
listType="picture-card"
fileList={fileList}
// onPreview={this.handlePreview}
onChange={this.handleChange}
>
{userDetails.imageId ? uploadedImage : uploadButton}
</MyUpload>
</div>
);
}
}

How to use $nuxt.$loading from axios interceptor

I would like to use $nuxt.$loading https://nuxtjs.org/api/configuration-loading/ outside of Vue component. I created central js for hitting APIs.
services/api-client.js
import axios from "axios";
import { state } from '../store/modules/sessions';
const axiosClient = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Api-Key': state().token
}
});
axiosClient.interceptors.request.use(function (config) {
// show nuxt loading here
return config;
}, function (error) {
return Promise.reject(error);
});
axiosClient.interceptors.response.use(function (response) {
// hide nuxt loading here
if (response.data.status.code != 200) {
throw { message: response.data.status.errorDetail };
} else {
return response;
}
}, function (error) {
// hide nuxt loading here
return Promise.reject(error);
});
export default {
all(path) {
return axiosClient.get(path);
},
show(path) {
return this.all(path);
},
create(path, params) {
return axiosClient.post(path, params);
},
update(path, params) {
return axiosClient.put(path, params);
}
};
and from my index.vue I'm dispatching the actions which trigger the Api Request.
<template>
<div>
<h1> Welcome </h1>
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('getInsiders', this);
}
}
</script>
The solution to your problem is this code below.
Please try this.
export default function({ $axios, store }: any) {
$axios.onRequest((config: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.start()
return config
})
})
$axios.onResponse((response: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return response
})
})
$axios.onError((error: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return Promise.reject(error)
})
})
}
Do you really need to declare own axios client?
Standard way how to do this is using nuxt's axios module and then customize it in your plugin.
nuxt.config.js
modules: ['#nuxtjs/axios'],
plugins: ['~/plugins/axios']
~/plugins/axios
export default ({ $axios, redirect }) => {
$axios.onError(error => {
// do anything you need
})
}
The axios module will manage loading status automatically.
Although you still can disable progress for individual requests
Eg from component/action
await this.$axios.$get('/myapi', { progress: false })