issue with slowly geting data from api to vue view - laravel

I have issue with very slowly getting data from laravel api to vue view, I did tutorial where I have:
import axios from 'axios';
const client = axios.create({
baseURL: '/api',
});
export default {
all(params) {
return client.get('users', params);
},
find(id) {
return client.get(`users/${id}`);
},
update(id, data) {
return client.put(`users/${id}`, data);
},
delete(id) {
return client.delete(`users/${id}`);
},
};
<script>
import api from "../api/users";
export default {
data() {
return {
message: null,
loaded: false,
saving: false,
user: {
id: null,
name: "",
email: ""
}
};
},
methods: {
onDelete() {
this.saving = true;
api.delete(this.user.id).then(response => {
this.message = "User Deleted";
setTimeout(() => this.$router.push({ name: "users.index" }), 1000);
});
},
onSubmit(event) {
this.saving = true;
api
.update(this.user.id, {
name: this.user.name,
email: this.user.email
})
.then(response => {
this.message = "User updated";
setTimeout(() => (this.message = null), 10000);
this.user = response.data.data;
})
.catch(error => {
console.log(error);
})
.then(_ => (this.saving = false));
}
},
created() {
api.find(this.$route.params.id).then(response => {
this.loaded = true;
this.user = response.data.data;
});
}
};
</script>
It's load data from api very slowly I see firstly empty inputs in view and after some short time I see data, if I open api data from laravel I see data immediately, so my question is How speed up it? Or maby I did something wrong?

Whenever I am using an API with Vue, I usually make most of my API calls before opening the Vue then passing it in like this.
<vue-component :user="'{!! $user_data !!}'"></vue-component>
But if you have to do it in the Vue component, I am not sure if this will show improvement over your method but I would set it up with the "mounted" like so.
export default {
mounted() {
api.find(this.$route.params.id).then(response => {
this.loaded = true;
this.user = response.data.data;
});
}
}
Also heres a good tutorial on Axios and how to use HTTP Requets with Vue.
Hopefully this answered your question, good luck!

Related

Vue 3 components not awaiting for state to be loaded

I am having some trouble using fetch in vuex to build state before rendering my page's components.
Here is the page component code:
async beforeCreate() {
await this.$store.dispatch('projects/getProjects');
},
And this is the state code it's dispatching:
async getProjects(context: any, parms: any) {
context.commit("loadingStatus", true, { root: true });
console.log("1");
await fetch(`${process.env.VUE_APP_API}/projects?`, {
method: "get",
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((response) => {
console.log("2");
if (!response.ok) {
throw new Error(response.status.toString());
} else {
return response.json();
}
})
.catch((error) => {
// todo: tratamento de erros na UI
console.error("There was an error!", error);
})
.then((data) => {
context.commit("setProjects", { data });
console.log("3");
// sets the active project based on local storage
if (
localStorage.getItem(
`activeProjectId_${context.rootState.auth.operator.accountId}`
)
) {
console.log("setting project to storage");
context.dispatch("selectProject", {
projectId: localStorage.getItem(
`activeProjectId_${context.rootState.auth.operator.accountId}`
),
});
} else {
//or based on the first item in the list
console.log("setting project to default");
if (data.length > 0) {
context.dispatch("selectProject", {
projectId: data[0].id,
});
}
}
context.commit("loadingStatus", false, { root: true });
});
},
async selectProject(context: any, parms: any) {
console.log("4");
context.commit("loadingStatus", true, { root: true });
const pjt = context.state.projects.filter(
(project: any) => project.id === parms.projectId
);
if (pjt.length > 0) {
console.log("Project found");
await context.commit("setActiveProject", pjt[0]);
} else if (context.state.projects.length > 0) {
console.log("Project not found setting first on the list");
await context.commit("setActiveProject", context.state.projects[0]);
} else {
await context.commit("resetActiveProject");
}
await context.commit("loadingStatus", false, { root: true });
},
I've added this console.log (1, 2, 3, 4) to help me debug what's going on.
Right after console.logging "1", it starts to mount the components. And I only get logs 2, 3 and 4 after all components have been loaded.
How can I make it so that my components will only load after the whole process is done (i.e. after I log "4") ?
If your beforeCreate hook (or any client hooks) contains async code, Vue will NOT wait to it then render and mount the component.
The right choice here should be showing a loader when your data is fetching from the server. It will provide better UX:
<template>
<div v-if="!data"> Loading... </div>
<div v-else> Put all your logic with data here </div>
</template>
<script>
export default {
data() {
return {
data: null
}
},
async beforeCreate() {
this.data = await this.$store.dispatch('projects/getProjects');
},
}
</script>

Laravel and Vue Pagination Fail

When I press the page 2 on pagination component, page=2 data comes to application and data never shows up on screen and pagination goes to 1 and everything is going to start point. I use bootstrap-vue for ui component library.
pagination component image:
Vue data variables:
isBusy: false,
output: {
message: "",
status: false
},
currentPage: 1,
tableData:{},
laravel api routes
Route::prefix('/pcustomers')->group( function() {
Route::post('/load', 'App\Http\Controllers\EmailListController#postPCustomer');
Route::middleware('auth:api')->post('/post', 'App\Http\Controllers\EmailListController#postPCustomer')->middleware(['web']);
Route::middleware('auth:api')->get('/all', 'App\Http\Controllers\EmailListController#allPCustomers')->middleware(['web']);
Route::middleware('auth:api')->get('/pcdata', 'App\Http\Controllers\EmailListController#pcData')->middleware(['web']);
Route::middleware('auth:api')->get('/delete', 'App\Http\Controllers\EmailListController#deletePCustomer')->middleware(['web']);
});
EmailListController function
public function pcData()
{
$pcData = DB::table('email_list')
->join('users', 'users.id', '=', 'email_list.workerId')
->select('email_list.*', 'users.username')
->paginate(100);
return response()->json($pcData);
}
Pagination component:
<b-pagination
v-model="currentPage"
:total-rows="tableData.total"
:per-page="tableData.to"
#input="getNewPageData()"
first-number
last-number
>
</b-pagination>
Here the axios post method for getting the data
getNewPageData(){
let list = this;
list.isBusy = true;
const page = list.currentPage;
axios.get("api/v1/pcustomers/pcdata?page="+page)
.then(function (response) {
list.tableData = response.data;
list.isBusy = false;
})
.catch(function (error) {
list.output = error;
});
},
It works at here for page 1
created(){
this.getNewPageData(this.currentPage);
}
Data Response for page 1:
{
"current_page":1,
"data":[
"..."
],
"first_page_url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=1",
"from":1,
"last_page":4,
"last_page_url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=4",
"links":[
{
"url":null,
"label":"Previous",
"active":false
},
{
"url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=1",
"label":1,
"active":true
},
{
"url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=2",
"label":2,
"active":false
},
{
"url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=3",
"label":3,
"active":false
},
{
"url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=4",
"label":4,
"active":false
},
{
"url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=2",
"label":"Next",
"active":false
}
],
"next_page_url":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata?page=2",
"path":"http:\/\/127.0.0.1:8000\/api\/v1\/pcustomers\/pcdata",
"per_page":100,
"prev_page_url":null,
"to":100,
"total":366
}
I did some changes on b-table and axios and it works now.
<b-table
id="pcustomers-table"
ref="pcustomers-table"
:busy="isBusy"
:items="tableData"
:fields="fields"
:per-page="resourceData.per_page"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
small
striped
hover
>
I removed :current-page="currentPage" here.
getPCustomersResourceData(){
let list = this;
list.isBusy = true;
const page = list.currentPage;
axios.get("api/v1/pcustomers/pcdata?page="+page)
.then(function (response) {
list.resourceData = response.data;
list.tableData = list.resourceData.data;
list.isBusy = false;
})
.catch(function (error) {
list.output = error;
});
},
I get the whole data and separate here like:
resourceData: {},
tableData:[],
Thanks to github.com/gilbitron and Kamlesh Paul.

How to properly pass input values to a function using the composition api in vue?

I have an input field that contains a postcode. On submit I want to pass the postcode as an object to an axios request. I have created a CodeSandbox here: https://codesandbox.io/s/determined-beaver-8ebqc
The relevant code is:
App.vue
<template>
<div id="app">
<input v-model="postcode" type="text" placeholder="Enter Postcode">
<button #click="getAddress">Submit</button>
</div>
</template>
<script>
import useAxios from "#/composition/use-axios";
export default {
name: "App",
setup() {
const { sentData, response, fetchData } = useAxios(
"api/address/lookup-address",
"postcode",
"Failed Finding Address"
);
return {
postcode: sentData,
address: response,
getAddress: fetchData
};
}
};
</script>
use-axios.js
import { reactive, toRefs } from "#vue/composition-api";
import axios from "axios";
export default function (url, objectData, errorMessage) {
const state = reactive({
sentData: null,
response: null
});
const fetchData = async () => {
console.log("Sent Data:", state.sentData);
console.log("Response:", state.response);
console.log("URL:", url);
console.log("Object Data:", objectData);
console.log("Object:", { [objectData]: state.sentData });
console.log("Error Message:", errorMessage);
const config = { headers: { "Content-Type": "application/json" } };
try {
const res = await axios.post(url, [objectData]: state.sentData, config);
state.response = await res.data;
} catch (error) {
// Error handling stuff
}
}
return { ...toRefs(state), fetchData };
}
Converting the postcode input string to an object in this way seems very hacky. Also, this would get very messy if I needed to send multiple parameters to the axios request. Say if I want to pass { id: "1234", user: "Me" }, I would like to be able to construct that like:
sentData = { id: ${id}, user: ${user} }
But I'm not able to do this. What is the proper way to do this so that I can keep use-axios generic?
You will need to import ref, reactive and computed from the composition-api and then use them like this:
<script>
import useAxios from "#/composition/use-axios";
import { ref, reactive, computed } from "#vue/composition-api";
export default {
name: "App",
setup() {
let object = ref("");
let state = reactive({ postcode: "" });
const sentDataObject = computed(() => {
state.postcode = object;
return state;
});
const addressList = useAxios(
"api/address/lookup-address",
sentDataObject.value,
"Failed Finding Address"
);
return {
addresses: addressList.response,
postcode: object,
getAddress: addressList.fetchData
};
}
};
</script>
change use-axios.js to:
import { reactive, toRefs } from "#vue/composition-api";
import axios from "axios";
export default function (url, objectData, errorMessage) {
const state = reactive({
sentData: null,
response: null
});
const fetchData = async () => {
console.log("Sent Data:", state.sentData);
console.log("Response:", state.response);
console.log("URL:", url);
console.log("Object Data:", objectData);
console.log("Error Message:", errorMessage);
const config = { headers: { "Content-Type":
"application/json" } };
try {
const res = await axios.post(url, objectData, config);
state.response = await res.data;
} catch (error) {
// Error handling stuff
}
};
return { ...toRefs(state), fetchData };
}
See Codesandbox demo here: https://codesandbox.io/s/dawn-glade-ewzb7

Nest.js handling errors for HttpService

I'm trying to test NestJS's built in HttpService (which is based on Axios). I'm having trouble testing error/exception states though. In my test suite I have:
let client: SomeClearingFirmClient;
const mockConfigService = {
get: jest.fn((type) => {
switch(type) {
case 'someApiBaseUrl': {
return 'http://example.com'
}
case 'someAddAccountEndpoint': {
return '/ClientAccounts/Add';
}
case 'someApiKey': {
return 'some-api-key';
}
default:
return 'test';
}
}),
};
const successfulAdd: AxiosResponse = {
data: {
batchNo: '39cba402-bfa9-424c-b265-1c98204df7ea',
warning: '',
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
};
const failAddAuth: AxiosError = {
code: '401',
config: {},
name: '',
message: 'Not Authorized',
}
const mockHttpService = {
post: jest.fn(),
get: jest.fn(),
}
it('Handles a failure', async () => {
expect.assertions(1);
mockHttpService.post = jest.fn(() => of(failAddAuth));
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useValue: mockConfigService,
},
{
provide: HttpService,
useValue: mockHttpService,
},
SomeClearingFirmClient,
],
}).compile();
client = module.get<SomeClearingFirmClient>(SomeClearingFirmClient);
const payload = new SomeClearingPayload();
try {
await client.addAccount(payload);
} catch(e) {
console.log('e', e);
}
});
And my implementation is:
async addAccount(payload: any): Promise<SomeAddResponse> {
const addAccountEndpoint = this.configService.get('api.someAddAccountEndpoint');
const url = `${this.baseUrl}${addAccountEndpoint}?apiKey=${this.apiKey}`;
const config = {
headers: {
'Content-Type': 'application/json',
}
};
const response = this.httpService.post(url, payload, config)
.pipe(
map(res => {
return res.data;
}),
catchError(e => {
throw new HttpException(e.response.data, e.response.status);
}),
).toPromise().catch(e => {
throw new HttpException(e.message, e.code);
});
return response;
}
Regardless of whether I use Observables or Promises, I can't get anything to catch. 4xx level errors sail on through as a success. I feel like I remember Axios adding some sort of config option to reject/send an Observable error to subscribers on failures... but I could be imagining that. Am I doing something wrong in my test harness? The other StackOverflow posts I've seen seem to say that piping through catchError should do the trick, but my errors are going through the map operator.
Your mockHttpService seems to return no error, but a value:
mockHttpService.post = jest.fn(() => of(failAddAuth));
What of(failAddAuth) does is to emit a value(failAddAuth) and then complete.
That's why the catchError from this.httpService.post(url, payload, config) will never be reached, because no errors occur.
In order to make sure that catchError is hit, the observable returned by post() must emit an error notification.
You could try this:
// Something to comply with `HttpException`'s arguments
const err = { response: 'resp', status: '4xx' };
mockHttpService.post = jest.fn(() => throwError(err));
throwError(err) is the same as new Observable(s => s.error(err))(Source code).

Error TypeError: Cannot read property 'dispatch' of undefined at app.js:12012

Hi I've been trying to learn vuejs and vuex while trying to get response from an api call with vuex concept I got the following error.Please help.
This error occurred
Error TypeError: Cannot read property 'dispatch' of undefined
at app.js:12012
loginAction.js
export const getUsersList = function (store) {
let url = '/Apis/allUsers';
Vue.http.get(url).then((response) => {
store.dispatch('GET_USER_RES', response.data);
if (response.status == 200) {
}
}).catch((response) => {
console.log('Error', response)
})
}
loginStore.js
const state = {
userResponse: []
}
const mutations = {
GET_USER_RES (state, userResponse) {
state.userResponse = userResponse;
}
}
export default {
state, mutations
}
login.vue
import {getUsersList} from './loginAction';
export default {
created () {
try{
getUsersList();
}catch(e){
console.log(e);
}
},
vuex: {
getters: {
getUsersList: state => state.userResponse
},
actions: {
getUsersList
}
}
}
</ script>
If you call the actions manually (like in your try/catch) they'll not get the store context as the first argument. You could use getUsersList(this.store) I think, but instead I would use dispatch to reach all your actions. (I edited just a little bit to get a minimal running example, but I think you get the point!)
new Vue({
render: h => h(App),
created() {
this.$store.dispatch('getUsersList');
},
store: new Vuex.Store({
getters: {
getUsersList: state => state.userResponse
},
actions: {
getUsersList
}
})
}).$mount("#app");
Also, use commit to reach the mutations instead of dispatch. ie:
export const getUsersList = function ({commit}) {
let url = '/Apis/allUsers';
Vue.http.get(url).then((response) => {
commit('GET_USER_RES', response.data); // because GET_USER_RES is a mutation
...

Resources