Pass on the form data from one component to another - vue.js

I'm trying to get information from a form via button which is not part of the component where the form is present.
Basically the current structure looks like this:
->Component A (button is here)
--> Inside component A I import another component (B) where the form is located
So I am trying to pass the information from component B, using button which is located in component A.
The reason why component B is not just part of component A (would make things relatively easier) is because it's used in several other places.
In component A I have:
`<ComponentB></ComponentB>
<div class="padding">
<button #onSubmit="onSubmit" class="add-button btn btn-md float-
right"
type="submit">Add items from component B</button>
</div>`
This is my data located in export default in component A
import ComponentB from './ComponentB';
import {mapActions} from 'vuex';
export default {
name: "ComponentA",
components:{
ComponentB
},
data(){
return{
firstname: '',
surname: '',
dateOfBirth: '',
isPolicyHolder: '',
drivingLicence:
licenceNumber: '',
countryLicenced: '',
coverEffectiveFrom: '',
coverEffectiveTo: '',
}
}
I also have vuex state where I'm trying to pass the information to on button click.
In Component A this is the method:
`methods:{
...mapActions(['addDriver']),
onSubmit(e){
e.preventDefault();
this.addDriver( this.firstname,
this.surname,
this.dateOfBirth,
this.isPolicyHolder,
this.licenceNumber,
this.countryLicenced,
this.coverEffectiveFrom,
this.coverEffectiveTo)
}
}`
Component B just hold few text boxes and date pickers.
So to summarise, on button click it should take whatever info is typed in component B to component A data.

You have to create a Vuex state to manage the variables. And then, create the actions and mutates to modify the state. After that, you can just get what you want in component B.
const store = new Vuex.Store({
state: {
// your states
},
mutations: {
// change state here sync
},
actions: {
// call mutations here, but you can do this async as axios methods etc..
},
getters: {
// need this only if state still needs some calculation.
}
}
So after you create all this. You'll be able to call the actions like above
this.$store.dispatch('ACTION')
and get the state in component B
this.$store.state.stateYouWant
or with getters
this.$store.getters.getterYouCreated

Related

Access component parameters inside caller component

I have a component which I call inside another component as:
COMPONENT 1
<template>
<FiltersComponent />
</template>
export default Vue.extend({
components: { FiltersComponent }
)}
So, this FiltersComponents have some parameters I want to access into my component one
COMPONENT 2 DATA
data() {
return {
TestList: [] as string[],
Test2List: null as string[] | null,
}
},
How can I access that TestList and Test2List inside COMPONENT 1?
There are multiple possibilities: If one component is a child component or sibling of the other, you might want to take a loop at props (passing data down) and events (passing data up). Otherwise, if they are not siblings or children, you can use a store like vuex.
To use the docs example:
vue entry point: (e.g., app.js):
import { createApp } from 'vue'
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
someProperty: 'someValue'
}
},
mutations: {
update (state, value) {
state.someProperty = value
}
}
})
const app = createApp({ /* your root component */ })
// Install the store instance as a plugin
app.use(store)
In your component's script-section:
this.$store.commit('update', 'YOUR_VALUE')
Other component:
const val = this.$store.state.someProperty
However, this is only a very basic example. You should definitely check out the docs, especially the sections about state, getters and mutations.
Always store the state as high in the component tree as you need, meaning that if you need these lists inside component 1, store them there. Then, you can use props and events to access and update the data inside component 2.
Alternatively, you can use a centralized store like Vuex.
You can achieve the same using ref,
Check the below example
<template>
<FiltersComponent ref="childComponent" /> <!-- Adding ref over here -->
</template>
export default Vue.extend({
components: { FiltersComponent }
)}
then in any of your methods or mounted section in Component 1. You can access Component2 data like
this.$refs.childComponent.TestList
or
this.$refs.childComponent.Test2List
So simple isn't it? ;)

Parent component updates a child component v-for list, the new list is not rendered in the viewport (vue.js)

My app structure is as follows. The Parent app has an editable form, with a child component list placed at the side. The child component is a list of students in a table.
I'm trying to update a child component list. The child component uses a 'v-for', the list is generated through a web service call using Axios.
In my parent component, I am editing a students name, but the students new name is not reflected in the List that I have on screen.
Example:
Notice on the left the parent form has the updated name now stored in the DB. However, the list (child component) remains unchanged.
I have tried a few things such as using props, ref etc. I am starting to think that my app architecture may be incorrect.
Does anyone know how I might go about solving this issue.
Sections of the code below. You may understand that I am a novice at Vue.
Assistance much appreciated.
// Child component
<component>
..
<tr v-for="student in Students.slice().reverse()" :key="student._id">
..
</component>
export default {
env: '',
// list: this.Students,
props: {
inputData: Boolean,
},
data() {
return {
Students: [],
};
},
created() {
// AXIOS web call...
},
};
// Parent component
import List from "./components/students/listTerms";
export default {
name: "App",
components: {
Header,
Footer,
List,
},
};
// Implementation
<List />
I think that it is better to use vuex for this case and make changes with mutations. Because when you change an object in the data array, it is not overwritten. reactivity doesn't work that way read more about it here
If your list component doesn't make a fresh API call each time the form is submitted, the data won't reflect the changes. However, making a separate request each time doesn't make much sense when the component is a child of the form component.
To utilise Vue's reactivity and prevent overhead, it would be best to use props.
As a simplified example:
// Child component
<template>
...
<tr v-for="student in [...students].reverse()" :key="student._id">
...
</template>
<script>
export default {
props: {
students: Array,
},
};
</script>
// Parent component
<template>
<div>
<form #submit.prevent="submitForm">
<input v-model="studentData.name" />
<input type="submit" value="SUBMIT" />
</form>
<List :students="students" />
</div>
</template>
<script>
import List from "./components/students/listTerms";
export default {
name: "App",
components: {
List,
},
data() {
return {
students: [],
studentData: {
name: ''
}
}
},
methods: {
submitForm() {
this.$axios.post('/endpoint', this.studentData).then(() => {
this.students.push({ ...this.studentData });
}).catch(err => {
console.error(err)
})
}
}
};
</script>
Working example.
This ensures data that isn't stored successfully won't be displayed and data that is stored successfully reflects in the child component.

Get imported components programmatically

I would like to be able to access the root components objects in a component. For instance, In the following component, I import and register two child components, now how do I get the components property in the mounted hook?
<template>
<div>
<SomeComponent />
<AnotherComponent />
</div>
</template>
<script>
import SomeComponent from '#/components/home/SomeComponent';
import AnotherComponent from '#/components/home/AnotherComponent';
export default {
components: { SomeComponent, AnotherComponent }, // I need to access this from the mounted hook
mounted() {
console.log('registered components:', Object.keys(componentsObject))
}
}
</script>
I have tried this.components, but that returns undefined, and then I tried just logging this to see what's available, but there's no components property, or anything that resembles what I'm after, so I dunno if there's some other way I could access it?
UPDATE: The reason that I want to do this is that I'm essentially creating a component slideshow, so I want to do Object.keys(components).length to let me programmatically determine the amount of slides, without the need for a amount variable that I have to manually update every time I create another slide.
There is a better approach to this problem. You may insert a property called slidesData in your data() method in your parent component.
data() {
slidesData: [
{ id: 1, title: 'Sample Slide title', description: 'Lorem ipsum', ... },
{ id: 2, title: 'Sample Slide 2 title', description: 'Dolor sit amet', ... },
]
}
and with this sample data, you can create a reusable component called SlideComponent. You can then "v-for" the data and pass it to the SlideComponent and there, you can format the data to your liking.
Going back to your problem, with this approach, you don't need to access the "Slide" component itself, you just have to access the slideData instead. This is a "Vue way" to solve the problem.

Vue 2 triggering method on child component

I will be happy if i can either trigger and event or call a method within the vue-button component when the user object gets updated. I need to make sure that this only happens on this specific vue-button and not on any other ones on the page.
The button controls a state object that styles the button depending on response from the server. Will display red if an error response is returned or green if a successful response is returned. In ether case the button then is also disabled so users can't spam click it. I need to have the ability to reset the button from the parent when the user is updated.
I do not believe that a global event bus is the solution because i need granular control over which components respond to the event and this button is used in a lot of places.
<template>
<div class="row">
<div class="col-12">
<basic-input-field :resource="user" :set-resource="setUserProperty" property="name" label="Name"></basic-input-field>
<basic-input-field :resource="user" :set-resource="setUserProperty" property="email" label="Email"></basic-input-field>
<basic-input-field :resource="user" :set-resource="setUserProperty" property="password" label="Password"></basic-input-field>
<vue-button :on-click="updateUser" label="Save"></vue-button>
</div>
</div>
</template>
<script>
import axios from 'axios';
import basicInputField from '../general/forms/basic-input-field.vue';
import vueButton from '../general/buttons/vue-button.vue';
export default {
name: 'user',
data() {
return {
};
},
mixins: [],
components: {
basicInputField,
vueButton
},
computed: {
user() {
return this.$store.state.user;
},
},
mounted() {
this.$httpGet('user', {id: 5});
},
watch: {
'user': function (newUser) {
// I want to trigger an event inside the component vue-button
// I do not want to trigger then event in every vue-button component on the page just this vue-button
// I need to call a resetStatus method within the vue-button component when the user changes
// This would have worked in vue 1 with an event 'resetStatus' that would call the method in the vue-button component
this.$broadcast('resetStatus');
}
},
methods: {
setUserProperty(property, value) {
this.$store.commit('UPDATE_MODULE_RESOURCE', {module: 'user', resource: property, value: value});
},
updateUser() {
return this.$httpPut('user', {id: this.user.id}, this.user);
}
},
};
</script>

Two way binding between controller in routerview and parent

I have a single page application which consists of a:
A navigation component which contains a title and a menu
A router-view
I'd like each component being presented in the router view which correspond to a state in the router, to update the title of the navigation component. How to go about passing the parameters from the components in the router-view to the outer navigation component.
I'm using vue 2.0
I would recommending Vuex for this:
https://github.com/vuejs/vuex
This way you can access your Vuex store in the main component and display its title:
computed: {
title () {
return this.$store.state.title
}
}
Then you'd set up a mutation to update the title and call this wherever required in your components. Now as Vue is reactive the title within the nav component will update.
Below is an abbreviated example:
Vuex Store:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
title: ''
},
mutations: {
updateTitle (state, title) {
state.title = title
}
}
})
Your App Root:
import Vue from 'vue'
import store from './vuex/store'
new Vue({
el: '#app',
store,
})
Nav component:
export default {
computed: {
title () {
return this.$store.state.title
}
},
...
Any other component which needs to update your title:
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'updateTitle' // map this.updateTitle() to this.$store.commit('updateTitle')
]),
},
// if you wanted to call it on mount
mounted () {
this.updateTitle('hello')
},
...
You can just have a contain-all Main component which stores the current route, and pass it to the navigation and the router view component as the their props, then the situations are:
when Main's stored route changes, the nav will rerender accordingly.
for the router view component, watch the prop change, this.$router.push('/some/where') when it changes.
when the user clicks an item on the nav, it emits an event, along with the data (the route the user wants to go to), the parent receives the event, changes its stored route, which, according to the last 2 bullets, changes how nav and router view looks.
The routes have to be part of the parent, since vue uses a parent-to-child-only one-way data flow. Children can't communicate with each other without help from their common parent unless we use some approach described in the last paragrath.
For details on how to implement parent-child data sharing, see doc.
Above is the simplest solution, which gets dirty quickly when your routes go nested. If so, you'll need a global event bus for non-parent-child data passing, or, vuex if you're looking at a large app and want your global states contained well.