How transfer a selected option to another page in vue application? - vue.js

On the first page of my Vue application, I have a drop-down menu that contains a list of mailboxes.
I would like to save the value/text of the selection and use it as a parameter or variable on the Inbox page that I routed to.
Here is the drop-down code using v-autocomplete:
<v-autocomplete dense
filled
label="Choose Mailbox"
v-model="mailboxes"
:items="mailboxes"
item-text='mailbox'
item-value='mailbox'>
</v-autocomplete>
Here is the button as v-btn that routes to the Inbox page.
<v-btn rounded color="primary"
#click="$router.push('Inbox')">
Load Mailbox</v-btn>
How do I save the selected mailbox value to use on the routed-to Inbox page?

I suggest you to get started with Vuex :)
It's a library that share a reactive data object across the whole app.
Here is what it could look like for you:
// /store/index.js
export state: () => {
mailbox: '',
}
export mutation: () => {
SET_MAILBOX(state, mailbox) {
state.mailbox = mailbox
}
}
// your-page.vue
<template>
<v-autocomplete
v-model="mailboxes"
dense
filled
label="Choose Mailbox"
:items="mailboxes"
item-text='mailbox'
item-value='mailbox'>
</v-autocomplete>
</template>
<script>
export default {
computed: {
mailboxes: {
get() {
this.$store.state.mailbox // Get the value from the Vuex store
},
set(newMailbox) {
this.$store.commit('SET_MAILBOX', newMailbox) // Update the Vuex store
},
}
}
}
</script>

Passing the selected value as a route parameter:
$router.push({
name: "Inbox",
params: { selectedValue: YOUR_VALUE }
});
In the Inbox page, you can access it through:
$route.params.selectedValue

Other easy solution is to use browser local storage.
Vue Client-Side Storage

Related

How can I pass a variable value from a "page" to "layout" in Nuxt JS?

I'm a beginner in VUE and donnow this one is the correct syntax. I need the variable {{name}} to be set from a page. Which means I need to change the value of the variable page to page. How can I achieve that? Help me guys.
My "Layout" Code is like below -
<template>
<div class="login-page">
<div class="col1">{{ name }}</div>
<div class="col2">
<div class="content-box">
<nuxt />
</div>
</div>
</div>
</template>
<script>
export default {
props: ['name']
}
</script>
And my "Page" code is following -
<template>
<div>Welcome</div>
</template>
<script>
export default {
layout: 'login',
data: function() {
return {
name: 'Victor'
}
}
}
</script>
this can be achieved by using the vuex module. The layout have access to the vuex store, so once a page is open, you can call a mutation to set the page name and listen the name state in the layout component.
First the Vuex module, we can add a module by creating a file in the store folder,
in this case we are creating the page module:
// page.js file in the store folder
const state = {
name: ''
}
const mutations = {
setName(state, name) {
state.name = name
}
}
const getters = {
getName: (state) => state.name
}
export default {
state,
mutations,
getters
}
Now we can use the setPageName mutation to set the pageName value once a page reach the created hook (also can be the mounted hook):
// Page.vue page
<template>
<div>Welcome</div>
</template>
<script>
export default {
layout: 'login',
created() {
this.$store.commit('page/setName', 'Hello')
},
}
</script>
And in the layout component we have the computed property pageName (or name if we want):
<template>
<div class="login-page">
<div class="col1">{{ name }}</div>
<div class="col2">
<div class="content-box">
<nuxt />
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
name() {
return this.$store.getters['page/getName']
}
}
}
</script>
And it's done!
Answer to your question in the commets:
The idea behind modules is keep the related information to some functionality in one place. I.e Let's say you want to have name, title and subtitle for each page, so the page module state variable will be:
const state = { name: '', title: '', subtitle: ''}
Each variable can be updated with a mutation, declaring:
const mutations = {
setName(state, name) {
state.name = name
},
setPageTitle(state, title) {
state.title = title
},
setPageSubtitle(state, subtitle) {
state.subtitle = subtitle
},
}
And their values can be updated from any page with:
this.$store.commit('page/setPageTitle', 'A page title')
The same if you want to read the value:
computed: {
title() {
// you can get the variable state without a getter
// ['page'] is the module name, nuxt create the module name
// using the file name page.js
return this.$store.state['page'].title
}
}
The getters are good for format or filter information.
A new module can be added anytime if required, the idea behind vuex and the modules is to have a place with the information that is required in many places through the application, in one place. I.e. the application theme information, if the user select the light or dark theme, maybe the colors can be changed. You can read more about vuex with nuxt here: https://nuxtjs.org/guide/vuex-store/ and https://vuex.vuejs.org/

Child component does not re-render when props change in store

I am using Vue-js with require-js. I am trying to get data from vuex store into my cart component and render a component for each item in the store. But when I trigger a mutation from my body component to change the store, the data is being changed and the props of my cart component change, but the UI does not re-render.
This is my store:
state: {
users: {
user1: {
item: { date:null }
}
}
}
mutations: { setDate:function(state,payload){
var newState = state.users;
newState[user][item].date = payload.date
state.users = Object.assign({},newState)
} }
This is my cart component:
<template>
<div v-if="activeStep==1">
<p> Service Time: {{service.ServiceTime}} Min.</p>
<p>Date: {{service.date || 'Not Selected'}} </p>
<p>Time: {{service.time || 'Not Selected'}} </p>
<p>Prefered Staff: {{service.staff}} </p>
</div>
</template>
<script>
define(['Vue','vuex'],function(Vue,vuex){
return {
template: template,
computed: vuex.mapState(['activeStep']),
props: ['service'],
}
})
</script>
This is the parent of my cart component:
<template>
<div class="cart-user-body" >
<div class="cart-service" v-for="(service,key,index) in users[user]" :key="index">
<div class="cart-service-body">
<service-book-details :service="service"></service-book-details>
</div>
</div>
</div>
</template>
<script>
define([
'Vue','vuex','vue!./serviceBookDetails'
], function(Vue,vuex,serviceBookDetails) {
return {
template:template,
components: {
'service-book-details': serviceBookDetails
},
props: ['user'],
computed: vuex.mapState(['users']),
}
});
</script>
This is how I am triggering the mutation from my body component:
addDate(e) {
var payload = {
date: moment(e, "DD/MM/YYYY").format("Do MMMM YYYY"),
id: this.$data.class,
name: this.username
};
this.$store.commit("setDate", payload);
},
I even tried using Vue.set(state,'users',newState) but the UI does not re-render.
I have checked the Vue dev tools and I see that upon triggering the mutation, the props of my cart component have updated but it does not show on the UI.
If I try using getters, the key to the object does not exist as my store does not have the required data until user interacts with UI and adds data. And my cart component is always showing since the start so it shows me an error saying cant read property item of undefined.
Am I doing anything wrong or is there a different way to make it work.
You can't use array indexing for setting values with Vue. It is a restriction caused by Javascript.
This will not be reactive if user and or item did not exist when you created your store.
newState[user][item].date =
Instead, you need to use:
Vue.set(object, key, value)
In your case, you first need to ensure you set user and item with that method before assigning to date.

Good practice for re-usable form component in Vue

I'm trying to write a re-usable form component for a "user". I'd like to be able to use it in both an "edit" and a "create" flow. I'd pass it a user object, it would validate as a user enters/modifies the user data and it would update the user object. The parent component (e.g EditUser, CreateUser) will do the actual saving.
How should I do this without mutating props? I've avoided using events so far because I'd need to fire one on every user input, passing a copy of the user object back up to the parent (I think).
EDIT: adding some (non-working) code to demonstrate. I can't share my exact code.
Parent component
<template>
<div >
<h1>header</h1>
<MyFormComponent
v-model="user"
>
</MyFormComponent>
<button>Save</button>
</div>
</template>
<script>
export default {
data(){
return {
user: {
name: 'User 1'
}
}
}
}
</script>
Form component
<template>
<form>
<MyFormInputComponent
v-model="user.name"
></MyFormInputComponent>
</form>
</template>
<script>
export default {
props: ['user'],
model: {
prop: 'user'
}
}
</script>
Thanks!
I don't know exactly your context, but this is how I use to do:
First, you don't need both components Parent and Child. You can do all you want inside Form Component.
To deal with the differences between create and edit modes, an option is computed property based on current route (if they are different according to create/edit operations).
Using this property, you decide if data will be fetched from API, if delete button will has shown, the title of the page and so on.
Here is an example:
async created() {
if (this.isEditMode) {
// fetch form data from API according to User ID and copy to a local form
},
},
computed: {
formTitle() {
return (this.isEditMode ? 'Update' : 'Create') + ' User';
},
}

How to pass data from one view to another with the vue-router

When using the vue-router with .vue files, there is no documented way to pass data from one view/component to another.
Let's take the following setup...
main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
let routes = [
{
path: '/page1',
component: require('./views/Posts.vue')
},
{
path: '/page2',
component: require('./views/EditPost.vue')
}
];
let router = new VueRouter({
routes
});
new Vue({
el: '#main',
router
});
Posts.vue:
<template>
<div>
Posts.vue passing the ID to EditPost.vue: {{ postId }}
</div>
</template>
<script>
export default {
data() {
return {
allPostsHere: // Whatever...
}
}
}
</script>
EditPost.vue:
<template>
<div>
EditPost.vue received ID from Posts.vue: {{ receivedId }}
</div>
</template>
<script>
export default {
data() {
return {
receivedId: // This is where I need the ID from Posts.vue
}
}
}
</script>
Please note: It is not possible to receive the ID directly from the EditPost.vue, because it has to be selected from Posts.vue.
Question: How can I pass the ID from one view/component to the other?
A route can only be accessed via a URL and a URL has to be something user can type into the URL bar, therefore to pass a variable from one view component to another you have to use route params.
I assume you have a list of posts in Posts component and want to change page to edit a specific post in EditPost component.
The most basic setup would be to add a link in the post list to redirect to the edit page:
<div v-for="post in posts">
{{ post.title }}
<router-link :to="'/post/' + post.id + '/edit'">Edit</router-link>
</div>
Your routes would look like this:
[
{
path: '/posts',
component: require('./views/Posts.vue'),
},
{
path: '/post/:postId/edit',
component: require('./views/EditPost.vue'),
props: true,
},
]
The props configuration option is just to inform the Router to convert route params to component props. For more information see Passing props to route components.
Then in EditPost you'd accept the id and fetch the post from server.
export default {
props: ['postId'],
data() {
return {
post: null,
}
},
mounted() {
this.fetchPost();
},
methods: {
fetchPost() {
axios.get('/api/post/' + this.postId)
.then(response => this.post = response.data);
},
},
}
After the request has been completed, EditPost has its own copy which it can further process.
Note, that on every post edit and every time you enter the post list, you'll make a request to the server which in some cases may be unnecessary, because all needed information is already in the post list and doesn't change between requests. If you want to improve performance in such cases, I'd advise integrating Vuex into your app.
If you decide to do so, the components would look very similar, except instead of fetching the post to edit via an HTTP request, you'd retrieve it from the Vuex store. See Vuex documentation for more information.
if you don't want the params appear in the URL bar,you can use window.sessionStorage, window.localStorage or vuex.
Before you leave the view, set your parameters and get it after entering the new view.
You can use a prop on the <router-view :my-id="parentStoredId"></router-view> to pass down data present in the app.vue (main component). To change the parent data you need to emit a custom event comprising the value, from the childs (Posts.vue, EditPost.vue).
Another way is the Non Parent-Child Communication.
The way I prefer is Vuex. Even if it require you to learn the usage, it will repay back when the app grows.

Navigating vuejs SPA via routes that share component does not refresh component data as expected

I have a couple routes in my vuejs SPA that I have set up using vue-router:
/create/feedback
/edit/feedback/66a0660662674061b84e8ea2fface0e4
The component for each route is the same form with a bit of smarts to change form values based on the absence or present of the ID in the route (feedbackID, in my example).
I notice that when I click from the edit route to the create route, the data in my form does not clear.
Below is the gist of my route file
import FeedbackFormView from './components/FeedbackForm.vue'
// Routes
const routes = [
{
path: '/create/feedback',
component: FeedbackFormView,
name: 'FeedbackCreate',
meta: {
description: 'Create Feedback',
}
},
{
path: '/edit/feedback/:feedbackId',
component: FeedbackFormView,
name: 'FeedbackEdit',
meta: {
description: 'Edit Feedback Form'
},
props: true
}
]
export default routes
Below is the gist of my component
<template lang="html">
<div>
<form>
<input v-model="model.someProperty">
</form>
</div>
</template>
<script>
export default {
data() => ({model: {someProperty:''}}),
props: ['feedbackId'],
created() => {
if (!this.$props['feedbackId']) {
return;
}
// otherwise do ajax call and populate model
// ... details omitted
}
}
</script>
However, if I modify my component as follows, everything works as expected
<template lang="html">
<div>
<form>
<input v-model="model.someProperty">
</form>
</div>
</template>
<script>
export default {
data() => ({model: {someProperty:''}}),
props: ['feedbackId'],
created() => {
if (!this.$props['feedbackId']) {
return;
}
// otherwise do ajax call and populate model
// ... details omitted
},
watch: {
'$route' (to, from) {
if (to.path === '/create/feedback') {
this.model = {}
}
}
}
}
</script>
Why is this? Why do I need watch?
I would have though that changing routes would be sufficient as the purpose of routing is to mimic the semantic behavior of page navigation
You have same component for different routes, when you go to edit route from the create route component is already created and mounted so the state of the component doesn't clear up.
Your component can listen to route changes using $router provided by vue-router every time the route changes the watcher is called.
For those who come this later, the following answer addresses the issue I was facing:
Vue-Router: view returning to login page after page refresh