I am trying to pass a variable from a Parent (page) component to a Child (modal) component. After reading a few examples, this works fine. The variable in question is brought in from another component as a route param. If i refresh the page, the variable is lost and can no longer be passed to the child. My question is, is the best way to persist this using the store, or is it possible to persist another way if the user refreshed? Any help would be appreciated
Parent
<b-container>
<Modal v-show="displayModal" #close="closeModal">
<template v-slot:body>
<ExpressionCreate v-show="displayModal" :lender-id="lenderId" #close="closeModal"/>
</template>
</Modal>
<div class="card" style="width: 100%;">
<div class="card-header">
<h5>{{this.lenderName}}</h5>
<b-alert :show="this.loading" variant="info">Loading...</b-alert>
</div>
<b-btn block variant="success" #click="showCreateLenderModal">Add Expression</b-btn>
....
created () {
this.lenderId = this.$route.params.lenderId
...
navigate () {
router.go(-1)
},
showCreateLenderModal () {
this.displayModal = true
},
toggleDisplayModal (isShow) {
this.displayModal = isShow
},
async closeModal () {
this.displayModal = false
}
Child
<label>lender id:</label>{{this.lenderId}}
...
props: {
lenderId: {
type: Number
}
},
You can use VueSession to persist.
Simply persist your lender_id with
this.$session.set('lender_id', this.lender_id)
and you can get it later as
saved_lender = this.$session.get('lender_id')
if (saved_lender) {
// handle saved case here
}
You can use query params in URI by doing:
$route.push('/', { query: { param: 3123 } })
This will generate the URL /?param=3123 and this will work after refresh. Or, you can use vuex and vuex-localstorage plugin, that let you persist data in the browser, and you can close the browser and open again and data will be restored.
In order for the state of application to persist, I recommend that you use vuex to manage the state and use a library that abstracts the persisting to vue in a clean way. One popular library is vuex-persist. https://www.npmjs.com/package/vuex-persist
If you dont want to use a store, VueSession, a package linked here should do the trick
Related
If I’m on a page with the URL 'http://localhost:8080/item' and I’m clicking on the same link on this page, then the page does not reload.
I need to make that if I click on the same link, the page will reload.
My link:
<nuxt-link :to="/item">
Any insight will be welcome. Thanks!
Use key, something like:
<router-view :key="$route.params.yourCustomParam"/>
Also you can use something like:
<router-link :to="{ params: { yourCustomParam: Data.now } }" replace>link</router-link>
Remember to is passed router.push() and it accept an object also. Doing that, it is more declarative and controllable. I'm using this to decide if the page of component should be rerendered since they will based on id params obtained from URL entry, and my child component can still using nesting .
I recently tried to solve a similar issue and to overcome this I used Vuex with :key (ref).
Firstly, in your store you need a state property such as:
export const state = () => ({
componentUpdates: {
item: 0,
//can add more as needed
}
})
In general, you could use only one property across the app if you prefer it that way. Just remember that later on, the key value needs to be unique - that is in the case if you used this property for two or more components within one page, for example. In this case, you could do something like this :key="$store.getters.getComponentUpdates.item+'uniqueString'"
then a getter:
export const getters = {
getComponentUpdates(state) {
return state.updateComponent;
}
}
finally a mutatation:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update]++
}
}
Now we can utilise the reactive :key wherever needed.
But first in your nuxt-link lets add an event to trigger the mutation, note the usage of #click.native to trigger the click event:
<nuxt-link #click.native="$store.commit('updateComponent', { update: 'item'})" :to="/item">
Now in the item page, for example. Let's imagine there is a component that needs to be updated. In this case we would add :key to it:
<my-item :key="$store.getters.getComponentUpdates.item" />
That is it. As you can see this solution utilises the benefits of nuxt-link but also allows us to selectively update only parts of our page that need updates (we could update the entire page this way as well if needed).
In case if you needed to trigger the logic from mounted or initial load in general, then you could use computed property and :key to your div container, right inside the <template> of your page.
Add :key to the div:
<template>
<div :key="$store.getters.getComponentUpdates.item"></div>
</template>
Create computed property:
computed: {
updateItemPage() {
//run your initial instructions here as if you were doing it in mounted then return the getter
this.initialLoadMethod()
return this.$store.getters.getComponentUpdates.item
}
}
The final touch, which is not crucial but can be implemented in order to reset the state property:
export const mutations = {
updateComponent(state, payload) {
return state.componentUpdates[payload.update] >= 10
? state.componentUpdates[payload.update] = 0
: state.componentUpdates[payload.update]++
}
}
I'm working on a project which expects a lot of places where I have to implement upload file component with different styles. I want to create highly customizable component which design I can modify easily and also I don't want to repeat myself and want to extract all the common logic into one place.
At this moment I have a vue.js version 2.2.2 and bulma css framework. I have a basic implementation of this component with only one design available. It supports a few states which represents current upload status:
component is waiting for the input
upload started
upload finished successfully
upload failed
Also this component is responsible for the upload process.
At this my component has a lot of responsibilities:
1. it knows how to deal with statuses:
<p v-if="isWaiting">
...
</p>
<div v-if="isLoading" class="is-loading">
<p class="title">{{currentPercent}} %</p>
</div>
...
data() { return {
currentStatus = Statuses.waiting
}}
computed: {
isWaiting() {
return this.currentStatus === Statuses.waiting;
},
...
}
it knows how to upload the data and count the current percent of data which is already transfered:
selectedFileChanged: async function(event) {
if (event.target.files.length === 0) return;
this.currentStatus = Statuses.uploading;
this.selectedFile = event.target.files[0];
const formData = new FormData();
formData.append("file", this.selectedFile);
try {
const result = (await axios.post("some url", formData, {
onUploadProgress: progress => {
const loaded = progress.loaded;
const total = progress.total;
this.currentPercent = Math.floor((loaded * 100) / total);
}
})).data;
this.currentStatus = Statuses.uploaded;
this.$emit("fileUploaded", {
file: this.selectedFile,
payload: result
});
} catch (exception) {
this.currentStatus = Statuses.error;
}
}
it has only one style which I can use
you can find the full code here: https://codesandbox.io/s/vue-template-gz2gk
So my question is: how to build this component to have an opportunity to change it's style easily and how to deal with upload statuses?
It seems to me that:
1. the component shouldn't know that axios is and how to upload the data;
2. the component should only be responsible for the current status and how to display it
I can introduce a new upload service which will know how to upload the data and add a new prop (current status) for upload file component and change it from the parent component. But in this case I will write the same code for all instances of the component.
Does someone know best practices of how-to create such customizable component?
UPDATE 1
I've tried to implement this functionality using slots and ended up with: https://codesandbox.io/s/vue-template-pi7e9
The component still knows how to upload the data, but now I can change the style of upload component.
So the new question is: how to work with slots and do not transfer a lot of variables and how to deal with uploading. I don't want my component to know how to upload the data :(
I've finished the component in the followig way:
In my parent component I have two different styles for my component:
<div id="app" class="container box">
<upload-file url="url"></upload-file> <-- the default one with statuses outside -->
<upload-file url="url"> <-- custom one which look like a box with statuses in it -->
<template #fileselect="{status, change}">
<custom-file-upload :status="status" #change="change"></custom-file-upload>
</template> <-- I've used custom-file-upload component here and tnjected all the properties from default implementation -->
</upload-file>
</div>
My default file input is nothing but a slot with default implementation:
<template>
<div>
<slot name="fileselect" :change="selectedFileChanged" :status="status">
<input id="fileselect" #change="selectedFileChanged" class="file-input" type="file">
<div class="help is-info" v-if="isWaiting">Waiting</div>
<div class="help is-success" v-if="isUploaded">Uploaded</div>
<div class="help is-info" v-if="isUploading">Uploading {{currentPercent}} %</div>
<div class="help is-error" v-if="isFailed">Failed</div>
</slot>
</div>
</template>
and what is the code looks like:
name: "upload-file",
props: ["url"], // url which will be used in upload request
data() {
return {
currentStatus: 1,
selectedFile: null,
currentPercent: 0
};
},
computed: {
someOtherProperties,
status() {
return {
isWaiting: this.isWaiting, // this.currentStatus === 1
isUploaded: this.isUploaded,
isUploading: this.isUploading,
isFailed: this.isFailed,
currentPercent: this.currentPercent,
selectedFile: this.selectedFile
};
}
},
methods: {
selectedFileChanged: function(event) {
this.selectedFile = event.target.files[0];
const xhr = new XMLHttpRequest();
// some handlers for XHR
xhr.open("POST", this.url, true);
xhr.send(formData);
}
}
Now I can use the file upload component with different styling but it will encapsulate in a base implementation all the status handling and upload logic.
I hope this solution will help someone :)
To view the full version of the code, please follow https://codesandbox.io/s/vue-template-pi7e9
My Vue app has a dynamic tabs mechanism.
Users can create as many tabs as the want on the fly, each tab having its own state (eg "Pages").
I am using the <keep-alive> component to cache the different pages.
<keep-alive include="page">
<router-view :key="$route.params.id" />
</keep-alive>
But users can also "close" individual tab. As pages tend to store a lot of datas, I would like to delete the according page component from the cache, as the user close the tab.
How can I programmatically destroy a cached component inside keep-alive ?
You can call this.$destroy() before user close the tab and delete all of data and event binding in that one.
If you don't mind losing the state when a tab is added/removed, then you can try these:
Use v-if and turn off the keep-alive component and turn it back on in
nextTick
Use v-bind on the include list, and remove "page" and add it
back in nextTick
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
cachedViews is the array of the route component name
First when create a tab, cachedViews push the cached route name, when you switch the opened tab, the current route is cached.
Second when close the tab, cachedViews pop the cached route name, the route
component will destroyed.
There is no built-in function in keep-alive which allows you to clear a specific component from the cache.
However, you can clear the cache from the VNode directly inside the component you want to destroy by calling this function :
import Vue, { VNode } from 'vue'
interface KeepAlive extends Vue {
cache: { [key: string]: VNode }
keys: string[]
}
export default Vue.extend({
name: 'PageToDestroy',
...
methods: {
// Make sure you are not on this page anymore before calling it
clearPageFromKeepAlive() {
const myKey = this.$vnode.key as string
const keepAlive = this.$vnode.parent?.componentInstance as KeepAlive
delete keepAlive.cache[myKey]
keepAlive.keys = keepAlive.keys.filter((k) => k !== myKey)
this.$destroy()
}
},
})
For me, it doesn't cause any memory leaks and the component is not in the Vue.js devtools anymore.
based on the answer of #feasin, here is the setup I am using
template
<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>
script
import { ref, inject, watch } from "vue";
export default {
components: { CustomRouterLink },
setup() {
const cachedViewsDefault = ["Page1", "Page1", "Page3"];
var cachedViews = ref([]);
const auth = inject("auth");
// check whether user is logged in (REACTIVE!)
const isSignedIn = auth.isSignedIn;
// set the initial cache state
if (isSignedIn.value) {
cachedViews.value = cachedViewsDefault;
}
// clear the cache state
watch(isSignedIn, () => {
if (!isSignedIn.value) {
cachedViews.value = [];
} else {
cachedViews.value = cachedViewsDefault;
}
});
return {
cachedViews,
};
},
};
First I set the initial cached views value based on whether the user is logged in or not.. After the user logs-out I simply set the array value to an empty array.
When the user logs back in - I push the default array keys back into the array.
This example of course does not provide the login/logout functionality, it is only meant as a POC to to the solution proposed by the #feasin (which seems like a good approach to me)
Edit 19.01.2022
I now understand the shortcomings of such approach. It does not allow to gradually destroy a certain component. Given that we have a component named Item and it's path is Item/{id} - there is currently no native way (in Vuejs3) to remove, let's say a cached item with Id = 2. Follow up on this issue on the Github: https://github.com/vuejs/rfcs/discussions/283
Edit 20-21.01.2022
Note that you have to use the computed function for inclusion list. Otherwise the component will not ever be unmounted.
Here is the fiddle with the problem: https://jsfiddle.net/7f2d4c0t/4/
Here's fiddle with the fix: https://jsfiddle.net/mvj2z3pL/
return {
cachedViews: computed(() => Array.from(cachedViews.value)),
}
I am in a process to support multiple data source to show data in a component via props & store.
The idea is to build a list component, which will load the data from the store if no props present. Otherwise, show the data from props. Thus, I am ensuring reusability in the context of search functionality and normal listing view.
Here is the code looks like,
<template>
<div>
<li v-for="user in this.dataSource" :key="user.age">
{{ user.name }}
</li>
</div>
</template>
<script>
export default {
props: {
userData: {
type: Array,
default: null,
}
},
created() {
// dispatch action to get user data
this.$store.dispatch("GET_USER_DATA");
},
data() {
return {
dataSource: this.userData !== null ? this.userData: this.$store.state.users
};
}
};
As of now, the store holds just static data. But in the context of REST it will be async in nature. So sometimes, I see no data.
So my question is that is this logic of dataSource can be improved further?
Thanks
Robin.
You should change dataSource to computed property. It will automatically re-computed when this.userData is changed or this.$store.state.users is changed
computed: {
dataSource() {
return this.userData !== null ? this.userData: this.$store.state.users
}
}
For more information about computed in Vuejs, please check the document
This is a bad approach anyways.
Store should be used to contain the data and components should just "show" it and manipulate it through actions and mutations.
So in this case, I'd remove the props, created and data part and add a getter to the store which should be implemented in a computed in the component. Nothing fancy here.
You shouldn't need to manually load the data with a proper store setup. Always make a getter and "load" it into the component with a computed.
ok so I've learned that I'm not supposed to call a child's method but pass it props instead.
I've got (parent) :
<template>
<div id="main">
<Header :title ="title"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
name: 'app'
data: function () {
return {
title: true
}
},
methods: {
hideTitle: function () {
this.title = false
console.log(this.title)
},
showTitle: function () {
this.title = true
console.log(this.title)
}
}
}
</script>
and (child) :
<script>
export default {
name: 'Header',
props: ['title'],
created () {
console.log(this.title)
},
methods: {
}
}
</script>
the first console logs (inside the parent) print correctly on each method but the second console log within the child stays true all the time. I got this from : Pass data from parent to child component in vue.js
inside what method does the console.log need to be to be printed everytime the methods in the parent are triggered?
(this is why I wanted to go for method-calling, originally, by going with variables instead, we're potentially omitting valuable parts of the process such as optimization and a "when" for the execution(s!!) of our code. pontetally being the key word here, don't blow up on me, keep in mind that I'm learning.)
OLD:
I've browsed the web and I know there a a million different answers
and my point is with the latest version of vue none of those millions
of answers work.
either everything is deprecated or it just doesn't apply but I need a
solution.
How do you call a child method?
I have a 1 component = 1 file setup.
DOM is declared inside a <template> tag javascript is written inside
a <script> tag. I'm going off of vue-cli scaffolding.
latest method I've tried is #emit (sometimes paired with an #on
sometimes not) doesn't work :
child :
<script>
export default {
name: 'Header',
created () {
this.$on('hideTitlefinal', this.hideTitlefinal)
},
methods: {
hideTitlefinal: function () {
console.log('hideeeee')
},
showTitlefinal: function () {
console.log('shwowwww')
}
}
}
</script>
parent :
<template>
<div id="main">
<Header v-on:hideTitle="hideTitlefinal" v-on:showTitle="showTitlefinal"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
methods: {
hideTitle: function () {
this.$emit('hideTitle')
},
showTitle: function () {
this.$emit('showTitle')
}
}
}
</script>
console :
Uncaught TypeError: this.$emit is not a function
at Object.showTitle (Main.vue?1785:74)
at VueComponent.showTitle (LateralMenu.vue?c2ae:113)
at boundFn (vue.esm.js?efeb:186)
at invoker (vue.esm.js?efeb:1943)
at HTMLDivElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1778)
Please don't do this. You're thinking in terms of events. When x happens, do y. That's sooo jquery 2005 man. Vue still has all that stuff, but we're being invited to think in terms of a view model...
You want your state in a variable, in window scope, and you want reactive pipes linking your vue stuff to your state object. To toggle visibility, use a dynamic class binding, or v-if. Then think about how to represent your state. It could be as simple as having a property like store.titleVisible. But, you want to 'normalize' your store, and avoid relationships between items of state. So if title visibility really depends on something higher up, like an editMode or something, then just put the higher-up thing in the store, then create computed properties if you need them.
The goal is that you don't care when things happen. You just define the relationships between the markup and the store, then let Vue take care of it. The docs will tell you to use props for parent=>child and $emit for child=>parent communication. Truth is you don't need this until you have multiple instances of a component, or reusable components. Vue stuff talks to a store, not to other vue stuff. For single-use components, as for your root Vue, just use the data:.
Whenever you find yourself writing show/hide methods, you're doing it wrong. It's intuitive (because it's procedural), but you'll quickly appreciate how much better the MVVM approach is.