Prevent refetch api on route query param change - javascript

this is my page script:
created() {
this.fetchCategories();
},
methods: {
...mapActions(['fetchCategories']),
},
as you see when page loads it fetch API automatically.
and here I open modals based on route query changes:
watch: {
// open dialog on route query
loading({ status, type }) {
if (type === 'index' && !status) {
const formQuery = this.$route.query.form;
if (formQuery) {
if (formQuery === 'create') {
[this.dialog, this.formType] = [true, 0];
} else if (formQuery === 'edit' && this.$route.query.id) {
[this.dialog, this.formType] = [true, 1];
} else if (formQuery === 'delete' && this.$route.query.id) {
this.dialogDelete = true;
}
}
}
},
},
and with these methods I push router with query param:
openFormDialog(id = null) {
this.dialog = true;
if (id) {
this.formType = 1;
this.$router.push({ name: 'CategoryIndex', query: { form: 'edit', id } });
} else {
this.formType = 0;
this.$router.push({ name: 'CategoryIndex', query: { form: 'create' } });
}
},
openDeleteDialog(id) {
this.$router.push({ name: 'CategoryIndex', query: { form: 'delete', id } });
this.dialogDelete = true;
},
},
this method close modal and push router (remove query params):
close() {
this.$emit('input', false);
this.$router.push({ name: 'CategoryIndex' });
},
problem is that when I remove (or redirect back to 'CategoryIndex' page), page component will rerendered and it fetch API again. how can I fix that? what is the solution?

Related

Vue metaInfo undefined in watch

I am inserting vue-meta logic inside the current code and there seems to be a problem that metaInfo is not available yet when watch is triggered.
export default {
metaInfo () {
return {
title: this.title,
meta: [],
}
},
watch: {
article() {
if (this.article) {
this.generatedHtml = this.applySnippets();
}
},
},
computed: {
article() {
return this.$store.getters.CONTENT;
},
title() {
if (this.article !== null) {
return this.article.info.caption;
}
return '';
},
}
created() {
this.$store.commit('CLEAR_CONTENT');
this.$store.dispatch('FETCH_CONTENT', { slug: this.slug, component: this });
},
methods: {
applySnippets() {
if (!this.article.snippets || this.article.snippets.length === 0) {
return this.article.data.content;
}
this.article.snippets.forEach(snippet => {
if (snippet.type === 'meta')
this.metaInfo.meta.push(snippet.object);
});
},
It fails that this.metaInfo is undefined during this Vue lifecycle phase. What to do?
To access the metaInfo result in the Options API, use this.$metaInfo (note the $ prefix):
if (snippet.type === 'meta')
this.$metaInfo.meta.push(snippet.object);
👆
demo

How to know from which child is emitting an event in vue.js 3?

If I have a component:
<product-display
:premium="premium"
:cart="cart"
#add-to-cart='updateCart'
#remove-from-cart='removeById'>
</product-display>
with these two methods:
methods: {
removeFromCart() {
this.$emit('remove-from-cart', this.variants[this.selectedVariant].id)
},
addToCart() {
this.$emit('add-to-cart', this.variants[this.selectedVariant].id)
},
And the parent has these methods:
methods: {
updateCart (id) {
this.cart.push(id)
},
removeById(id) {
const index = this.cart.indexOf(id)
if (index > -1) {
this.cart.splice(index, 1)
}
}
}
Is there a way to remove one method from the parent and use only updateCart(id) knowing from which child is emitting the event?
So in the HTML you end up with:
#add-to-cart='updateCart'
#remove-from-cart='updateCart'
You can do that in a few different ways. My preferred one is like this:
// Component.vue
updateCart(action) {
this.$emit('update-cart', {
id: this.variants[this.selectedVariant].id), action
}
//Parent.vue
#update-cart='updateCart'
methods: {
updateCard({id, action}) {
if (action == 'add') {
return this.cart.push(id);
}
this.card = this.card.filter(x => x != id);
}
}

Unable to pass the required parameters into the URL - Laravel Inertia Vue

I am building a datatable with multi-column sorting functionality. As up to now, the sorting functionality is working fine, what I am unable to get is, right parameters into the url. As I am only passing $sorts to the component, as a prop, hence I'm using this.$inertia.get to pass the $sorts back to the controller, which is returning back the sorted data. But due to passing sorts: this.sorts within the Inertia get method, its returning back the url query as http://127.0.0.1:8000/users?sorts[name]=asc. How can I get the required parameter within the Inertia get method so I get a url query as suchhttp://127.0.0.1:8000/users?sort_field=name&sort_direction=asc as well as pass the $sorts as well so it returns back the expected data.
Controller
public $sorts = [];
public function initalizeSortingRequest()
{
$this->sorts = request()->get('sorts', $this->sorts);
}
public function applySorting($query)
{
foreach ($this->sorts as $sort_field => $sort_direction) {
$query->orderBy($sort_field, $sort_direction);
}
return $query;
}
Component
<script >
methods: {
sortBy(field) {
if (!this.sorts[field]) {
this.sorts[field] = 'asc';
} else if (this.sorts[field] == 'asc') {
this.sorts[field] = 'desc';
} else {
delete this.sorts[field];
}
let route = this.route('users.index', {
sorts: this.sorts
})
this.$inertia.get(route, {}, {
only: ['usersData'],
preserveState: true,
preserveScroll: true
})
}
}
</script>
I recently made a screencast on building a datatable with InertiaJS and Laravel.
The gist of it is:
import AppLayout from '#/Layouts/AppLayout';
import Pagination from '../Jetstream/Pagination';
import { pickBy, throttle } from 'lodash';
export default {
components: {
AppLayout,
Pagination,
},
props: {
users: Object,
filters: Object,
},
data() {
return {
params: {
search: this.filters.search,
field: this.filters.field,
direction: this.filters.direction,
},
};
},
methods: {
sort(field) {
this.params.field = field;
this.params.direction = this.params.direction === 'asc' ? 'desc' : 'asc';
},
},
watch: {
params: {
handler: throttle(function () {
let params = pickBy(this.params);
this.$inertia.get(this.route('users'), params, { replace: true, preserveState: true });
}, 150),
deep: true,
},
},
};
Then in the controller index action:
public function index()
{
request()->validate([
'direction' => ['in:asc,desc'],
'field' => ['in:name,city']
]);
$query = User::query();
if (request('search')) {
$query->where('name', 'LIKE', '%'.request('search').'%');
}
if (request()->has(['field', 'direction'])) {
$query->orderBy(request('field'), request('direction'));
}
return Inertia::render('Users', [
'users' => $query->paginate()->withQueryString(),
'filters' => request()->all(['search', 'field', 'direction'])
]);
}
You can watch the screencast here.

default value of autocomplete with vuetify

so i make an signup and address form for every user and i want every time the user is connected and navigate to the profile page, he will edit his details.
now i have async autocomplete from api that get for me all the items in object format,
so i tried to give the v-model an default value but it didn't change, i guess there is supposed to be connection between the items to the v-model, so i tried to fake the async search and get the items but still couldn't see the default value.
i don't care if the value wont be in the data i just want to see it visual
<script>
export default {
props: {
cmd: {
type: String
},
itemText: {
type: String
},
itemValue: {
type: String
},
label: {
type: String
},
city: {
type: Number
},
street: {
type: Number
},
type: {
type: String
},
defaultValue: {
type: String || Number
}
},
data() {
return {
loading: false,
items: [],
search: null,
select: null
};
},
watch: {
search(val) {
val && val !== this.select && this.querySelections(val);
},
select(val) {
if (val !== this.defaultValue) {
this.$emit("selected", { value: val[this.itemValue], text: val[this.itemText] });
}
}
},
async mounted() {
const defaultSelected = {};
defaultSelected[`${this.itemText}`] = this.defaultValue ? this.defaultValue.value : this.defaultValue;
defaultSelected[`${this.itemValue}`] = this.defaultValue ? this.defaultValue.id : this.defaultValue;
await this.querySelections(defaultSelected[`${this.itemText}`]);
console.log(this.items);
// this.select = defaultSelected[`${this.itemText}`];
},
methods: {
async querySelections(v) {
this.loading = true;
// Simulated ajax query
const data = (await this.$services.SearchService.searchAddress(v, this.cmd, this.city, this.street)).data;
console.log(data);
if (this.type === "city") {
this.items = data.Data[`CitiesRes`];
}
if (this.type === "street") {
this.items = data.Data[`StreetsRes`];
}
if (this.type === "streetNumber") {
this.items = data.Data["NumbersRes"];
}
this.loading = false;
},
asyncinsertDefualtData() {}
}
};
</script>
<template>
<v-autocomplete
v-model="select"
:loading="loading"
:items="items"
:search-input.sync="search"
:item-text="itemText"
:item-value="itemValue"
:value="defaultValue"
return-object
cache-items
flat
hide-no-data
hide-details
solo
:label="label"
></v-autocomplete>
</template>

Vue, Vuex & remote storage

Well a have some value in remote storage (lets say x) and b-form-checkbox that should control this value. I want to inform user if value actually changed on storage and time when it happens.
So basically I want:
When user check/uncheck b-form-checkbox I want to change state of b-form-checkbox, send async request to the remote storage and show some b-spinner to indicate that state isn't actually changed yet.
When I receive answer from remote storage:
if change was successful just hide b-spinner.
if change was not successful (timeouted, error on server, etc) I want to change b-form-checkbox state back (since value actually doesn't changed on storage) and hide b-spinner
What is the silliest way to do int using Vue + Vuex?
Currently I'm doing it this way:
xChanger.vue:
<template>
<b-form-checkbox v-model="xComp" switch>
{{xComp ? 'On' : 'Off'}}
<b-spinner v-if="!xSynced"/>
</b-form-checkbox>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
export default {
name: 'XChanger',
computed: {
...mapState(['x']),
...mapGetters(['xSynced']),
xComp: {
get() { return x.local },
set(value) {
if (value != this.x.local) {
this.setX(value)
}
},
},
},
methods: {
...mapActions(['setX']),
},
}
</script>
main.js
import Vuex from 'vuex'
import Axios from 'axios'
const store = new Vuex.Store({
state: {
x: {
remote: null,
local: null
},
getters: {
xSynced(state) {
state.x.local === state.x.remote
}
},
actions: {
async setX(store, value) {
store.state.x.local = value
try {
let response = await Axios.post('http://blah.blah/setX', {x: value});
if (response.status == 200) {
store.state.x.remote = value
}
} catch (error) {
store.state.x.local = !value
}
}
},
mutations: {
setX(state, value) {
state.x.local = value
state.x.remote = value
}
}
},
})
But it is too verbose for just one value to be controlled (especially computed property xComp). I'm sure that such a simple template should be already solved and has more simple way to implement.
Here is an example:
<template>
<b-form-checkbox v-model="x.local" switch>
{{x.local ? 'On' : 'Off'}}
<b-spinner v-if="saving"/>
</b-form-checkbox>
</template>
<script>
export default
{
name: 'XChanger',
data: () => ({
x:
{
local: false,
remote: false,
},
saving: false,
}),
watch:
{
'x.local'(newValue, oldValue)
{
if (newValue !== oldValue && newValue !== this.x.remote)
{
this.updateRemote(newValue);
}
}
}
methods:
{
async updateRemote(value)
{
try
{
this.saving = true;
const response = await Axios.post('http://blah.blah/setX', {x: value});
if (response.status == 200)
{
this.x.remote = value;
}
else
{
this.x.local = this.x.remote;
}
}
catch (error)
{
this.x.local = this.x.remote;
}
this.saving = false;
}
},
}
</script>

Categories

Resources