Rendering a list based on a shared property in Vue - javascript

I'd like to render out a list of components based on whether the type property is of a certain type. For example, if resource.type === 'article' then render all of the resources with type article, etc.
My resources array looks like this
I have created a component that is basically just a view.
<template>
<div class="type-feed">
<resource-card></resource-card>
</div>
</template>
<script>
import Vue from 'vue';
import VueFire from 'vuefire';
import ResourceCard from '../components/ResourceCard'
var resourcesRef = firebase.database().ref('resources');
// explicit installation required in module environments
Vue.use(VueFire);
export default {
name: 'type-view',
components: {
ResourceCard
},
data () {
return {
}
},
firebase: {
resources: resourcesRef
}
}
</script>
<style>
</style>
The <resource-card> component should take in the list and render a specific information on an individual resource object. Something like the following
<resource-card v-for="(resource, index) in resources" :resource="resource>
My question is, what is the best way to render a list by resources.type?

If <resource-card> is not used anywhere else, then just pass the resource to it and let it decide what to render according to resource.type should be enough.
If you could need a article card later elsewhere, or you want a more elegant design, then you may define a component for each resource type, and in the v-for, you can use dynamic components to render different components in one loop.

Related

Use <component> that was imported in the parent

I'm building a component that manages other components.
It dynamically render components in specific places depending on the props and inputs, much like an orchestrator.
Use case
My orchestrator have the following placeholders, like a grid (p1 ... p6):
|-p1-|-p2-|-p3-|
|-p4-|-p5-|-p6-|
In a given moment, it renders the component C1 into p2 and C2 into p6:
|-p1-|-C1-|-p3-|
|-p4-|-p5-|-C2-|
In another moment, it replaces the C1 by C3:
|-p1-|-C3-|-p3-|
|-p4-|-p5-|-C2-|
The problem
Given this dynamism, I can't (as far as I know) use slots. So I'm using component tags with the :is prop in order to dynamically render the correct component. The problem is that in order for the :is to work, I must have the component defined in my orchestrator/manager component. And I would like to keep it separated for reuse, doesn't make sense to import the components there. One solution would be to globally register the components. I would like to avoid that if possible. Is there a way? I'm open to any kind of reflection-magic you may think n_n'
You can just pass the component via a prop like this:
Orchestrator.vue
<component :is="comp"/>
export default {
props: ['comp']
}
Test.vue
<orchestrator :comp="MyComponent"/>
import Orchestrator from './orchestrator.vue'
import MyComponent from './my-component.vue'
export default {
components: {
Orchestrator,
},
data() {
return {
MyComponent,
};
},
}
You should be able to use async components, loaded dynamically.
For example...
<component :is="p2Replacement"></component>
data () {
return {
p2Replacement: null
}
},
methods: {
setP2Replacement(component) {
this.p2Replacement = () => import(`path/to/${component}.vue`)
}
}
And call it like setP2Replacement('C1') or setP2Replacement('C3').
The fixed parts in the import expression are required so Webpack can include the appropriate files using a glob pattern. See https://webpack.js.org/guides/dependency-management/#require-context

MobX store doesn't get updated in react

What i want to achieve
I have a store object and i want to populate one of it's properties (which is an empty array) with instances of another object. And i want one of my react component automatically updated when part of the mentioned array instance is changed.
What my problem is
By logging out the value of this constructed store object i can see the change in the browser console, but it doesn't get updated automatically in the react component when its value changed.
So i'd like to get hints, examples of how to implement a solution like this in general.
Details
My project
I want to create a MobX store called Session which would store everything my react webapp needs.
This webapp would be a document handling tool, so after creating new or loading existing documents i want to add the Document object to the Session (into an object array called documents).
Some more details: a Document consists of one or more section. So using a WYSIWYG editor i add its content to the given section every time it's content changes.
Problem
I can add a new Document to the Session, i can update section as well(I can log out the value of a section in console), but using the Session reference to this document and section in a react component it doesnt update its state when section value is changed.
To my understanding in my example the reference of a Document is not changed when the value of a section is changed and hence it doesn't trigger MobX to react.
What i found so far
I started to dig in the deep, dark web and found this article.
So i started getting excited since asStructure (or asMap) seemed to solve this issue, but it looks like asStructure in MobX is deprecated.
Then i found this issue thread, where a thing called observable.structurallyCompare is mentioned. But again i found nothing about this in MobX documentation so im puzzled how to implement it.
So im stuck again and have no idea how to solve this problem.
Code excerpts from my project
This is how i reference to the mentioned Session value in the main react component:
import userSession from '../client/Session';
import {observer} from 'mobx-react';
#observer class App extends React.Component {
...
render() {
return (
...
<div>{JSON.stringify(userSession.documents[0].content.sections)}</div>
...
This is how i update the section in the editor react component:
import userSession from '../../client/Session';
...
handleChange(value,arg2,arg3,arg4) {
this.setState({ content: value, delta: arg4.getHTML()});
userSession.documents[0].updateSectionContent(this.props.id, this.state.delta);
}
}
...
Session.js excerpt:
class Session {
constructor(){
extendObservable(this, {
user: {
},
layout: {
},
documents: []
})
//current hack to add an empty Document to Session
this.documents.push(new Document());
}
//yadayadayada...
#action addNewSection() {
userSession.documents[0].content.sections.push({
type: "editor",
id: userSession.documents[0].getNextSectionID(),
editable: true,
content: "",
placeholder: "type here to add new section..."
});
}
}
var userSession = window.userSession = new Session();
export default userSession;
Document.js
import {extendObservable, computed, action} from "mobx";
import userSession from "./Session";
class Document {
constructor(doc = null) {
if (doc == null) {
console.log("doc - no init value provided");
extendObservable(this,{
labels: {
title: "title",
description: "description",
owners: ["guest"]
},
content: {
sections: [
{
type: "editor",
id: "sec1",
editable: true,
placeholder: "this is where the magic happens"
},
]
}
})
} else {
console.log("doc - init values provided: ");
extendObservable(this,{
labels: doc.labels,
content: doc.content
});
}
}
getNextSectionID(){
return `sec${this.content.sections.length}`;
}
#action updateSectionContent(sectionID, delta) {
console.log("doc - looking for section " + sectionID + " to update with this: " + delta);
let index = this.content.sections.findIndex(
section => section.id === sectionID
);
userSession.documents[0].content.sections[index].content = delta;
}
}
export default Document;
Ps.: atm moment i don't remember why i made Document properties observable, but for some reason it was necessary.
Unfortunately, you are implementing mobx with React the incorrect way. To make it more comprehensible, I suggest you look into the implementation of the HOC observer that mobx-react provide.
Basically, what this HOC does is to wrap your component inside another React component that implement shouldComponentUpdate that check when the props referred inside render function change, and trigger the re-render. To make React component reactive to change in mobx store, you need to pass the store data to them as props, either in React traditional way, or via the inject HOC that mobx-react provide.
The problem while your code does not react to change in the store is because you import the store and use them directly inside your component, mobx-react cannot detect change that way.
To make it reactive, instead of import it and use it directly in your App component, you can pass the store as a prop as follow:
#observer
class App extends React.Component {
...
render() {
return (
...
<div>{this.props.sections}</div>
...);
}
}
Then pass the store to App when it's used:
<App sections={userSession.documents[0].content.sections} />
You can also have a look at how to use inject here: https://github.com/mobxjs/mobx-react.
An just a suggestion: before jumping directly on some best pattern, try to stick with the basic, standard way that library author recommend and understand how it works first, you can consider other option after you got the core idea.

How can give notice to the brother level component? [duplicate]

Overview
In Vue.js 2.x, model.sync will be deprecated.
So, what is a proper way to communicate between sibling components in Vue.js 2.x?
Background
As I understand Vue.js 2.x, the preferred method for sibling communication is to use a store or an event bus.
According to Evan (creator of Vue.js):
It's also worth mentioning "passing data between components" is
generally a bad idea, because in the end the data flow becomes
untrackable and very hard to debug.
If a piece of data needs to be shared by multiple components, prefer
global stores or Vuex.
[Link to discussion]
And:
.once and .sync are deprecated. Props are now always one-way down. To
produce side effects in the parent scope, a component needs to
explicitly emit an event instead of relying on implicit binding.
So, Evan suggests using $emit() and $on().
Concerns
What worries me is:
Each store and event has a global visibility (correct me if I'm wrong);
It's too wasteful to create a new store for each minor communication;
What I want is to some scope events or stores visibility for siblings components. (Or perhaps I didn't understand the above idea.)
Question
So, what is the correct way to communicate between sibling components?
You can even make it shorter and use the root Vue instance as the global Event Hub:
Component 1:
this.$root.$emit('eventing', data);
Component 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
With Vue.js 2.0, I'm using the eventHub mechanism as demonstrated in the documentation.
Define centralized event hub.
const eventHub = new Vue() // Single event hub
// Distribute to components using global mixin
Vue.mixin({
data: function () {
return {
eventHub: eventHub
}
}
})
Now in your component you can emit events with
this.eventHub.$emit('update', data)
And to listen you do
this.eventHub.$on('update', data => {
// do your thing
})
Update
Please see the answer by alex, which describes a simpler solution.
Disclaimer: this answer was written a long time ago and it may not reflect latest Vue development or trends. Take everything in this answer with a grain of salt and please comment if you find anything that's outdated, no longer valid, or unhelpful.
State scopes
When designing a Vue application (or in fact, any component based application), there are different types of data that depend on which concerns we're dealing with and each has its own preferred communication channels.
Global state: may include the logged in user, the current theme, etc.
Local state: form attributes, disabled button state, etc.
Note that part of the global state might end up in the local state at some point, and it could be passed down to child components as any other local state would, either in full or diluted to match the use-case.
Communication channels
A channel is a loose term I'll be using to refer to concrete implementations to exchange data around a Vue app.
Each implementation addresses a specific communication channel, which includes:
Global state
Parent-child
Child-parent
Siblings
Different concerns relate to different communication channels.
Props: Direct Parent-Child
The simplest communication channel in Vue for one-way data binding.
Events: Direct Child-Parent
Important notice: $on and $once were removed in Vue version 3.
$emit and v-on event listeners. The simplest communication channel for direct Child-Parent communication. Events enable 2-way data binding.
Provide/Inject: Global or distant local state
Added in Vue 2.2+, and really similar to React's context API, this could be used as a viable replacement to an event bus.
At any point within the components tree could a component provide some data, which any child down the line could access through the inject component's property.
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length)
}
}
})
app.component('todo-list-statistics', {
inject: ['todoLength'],
created() {
console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
}
})
This could be used to provide global state at the root of the app, or localized state within a subset of the tree.
Centralized store (Global state)
Note: Vuex 5 is going to be Pinia apparently. Stay tuned. (Tweet)
Vuex is a state management pattern + library for Vue.js applications.
It serves as a centralized store for all the components in an
application, with rules ensuring that the state can only be mutated in
a predictable fashion.
And now you ask:
[S]hould I create vuex store for each minor communication?
It really shines when dealing with global state, which includes but is not limited to:
data received from a backend,
global UI state like a theme,
any data persistence layer, e.g. saving to a backend or interfacing with local storage,
toast messages or notifications,
etc.
So your components can really focus on the things they're meant to be, managing user interfaces, while the global store can manage/use general business logic and offer a clear API through getters and actions.
It doesn't mean that you can't use it for component logic, but I would personally scope that logic to a namespaced Vuex module with only the necessary global UI state.
To avoid dealing with a big mess of everything in a global state, see the Application structure recommandations.
Refs and methods: Edge cases
Despite the existence of props and events, sometimes you might still
need to directly access a child component in JavaScript.
It is only meant as an escape hatch for direct child manipulation -
you should avoid accessing $refs from within templates or computed properties.
If you find yourself using refs and child methods quite often, it's probably time to lift the state up or consider the other ways described here or in the other answers.
$parent: Edge cases
Similar to $root, the $parent property can be used to access the
parent instance from a child. This can be tempting to reach for as a
lazy alternative to passing data with a prop.
In most cases, reaching into the parent makes your application more
difficult to debug and understand, especially if you mutate data in
the parent. When looking at that component later, it will be very
difficult to figure out where that mutation came from.
You could in fact navigate the whole tree structure using $parent, $ref or $root, but it would be akin to having everything global and likely become unmaintainable spaghetti.
Event bus: Global/distant local state
See #AlexMA's answer for up-to-date information about the event bus pattern.
This was the pattern in the past to pass props all over the place from far up down to deeply nested children components, with almost no other components needing these in between. Use sparingly for carefully selected data.
Be careful: Subsequent creation of components that are binding themselves to the event bus will be bound more than once--leading to multiple handlers triggered and leaks. I personally never felt the need for an event bus in all the single page apps I've designed in the past.
The following demonstrates how a simple mistake leads to a leak where the Item component still triggers even if removed from the DOM.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<button type="button" #click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
Remember to remove listeners in the destroyed lifecycle hook.
Component types
Disclaimer: the following "containers" versus "presentational" components is just one way to structure a project and there are now multiple alternatives, like the new Composition API that could effectively replace the "app specific containers" I'm describing below.
To orchestrates all these communications, to ease re-usability and testing, we could think of components as two different types.
App specific containers
Generic/presentational components
Again, it doesn't mean that a generic component should be reused or that an app specific container can't be reused, but they have different responsibilities.
App specific containers
Note: see the new Composition API as an alternative to these containers.
These are just simple Vue component that wraps other Vue components (generic or other app specific containers). This is where the Vuex store communication should happen and this container should communicate through other simpler means like props and event listeners.
These containers could even have no native DOM elements at all and let the generic components deal with the templating and user interactions.
scope somehow events or stores visibility for siblings components
This is where the scoping happens. Most components don't know about the store and this component should (mostly) use one namespaced store module with a limited set of getters and actions applied with the provided Vuex binding helpers.
Generic/presentational components
These should receive their data from props, make changes on their own local data, and emit simple events. Most of the time, they should not know a Vuex store exists at all.
They could also be called containers as their sole responsibility could be to dispatch to other UI components.
Sibling communication
So, after all this, how should we communicate between two sibling components?
It's easier to understand with an example: say we have an input box and its data should be shared across the app (siblings at different places in the tree) and persisted with a backend.
❌ Mixing concerns
Starting with the worst case scenario, our component would mix presentation and business logic.
// MyInput.vue
<template>
<div class="my-input">
<label>Data</label>
<input type="text"
:value="value"
:input="onChange($event.target.value)">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
value: "",
};
},
mounted() {
this.$root.$on('sync', data => {
this.value = data.myServerValue;
});
},
methods: {
onChange(value) {
this.value = value;
axios.post('http://example.com/api/update', {
myServerValue: value
});
}
}
}
</script>
While it might look fine for a simple app, it comes with a lot of drawbacks:
Explicitly uses the global axios instance
Hard-coded API inside the UI
Tightly coupled to the root component (event bus pattern)
Harder to do unit tests
✅ Separation of concerns
To separate these two concerns, we should wrap our component in an app specific container and keep the presentation logic into our generic input component.
With the following pattern, we can:
Easily test each concern with unit tests
Change the API without impacting components at all
Configure HTTP communications however you'd like (axios, fetch, adding middlewares, tests, etc)
Reuse the input component anywhere (reduced coupling)
React to state changes from anywhere in the app through the global store bindings
etc.
Our input component is now reusable and doesn't know about the backend nor the siblings.
// MyInput.vue
// the template is the same as above
<script>
export default {
props: {
initial: {
type: String,
default: ""
}
},
data() {
return {
value: this.initial,
};
},
methods: {
onChange(value) {
this.value = value;
this.$emit('change', value);
}
}
}
</script>
Our app specific container can now be the bridge between the business logic and the presentation communication.
// MyAppCard.vue
<template>
<div class="container">
<card-body>
<my-input :initial="serverValue" #change="updateState"></my-input>
<my-input :initial="otherValue" #change="updateState"></my-input>
</card-body>
<card-footer>
<my-button :disabled="!serverValue || !otherValue"
#click="saveState"></my-button>
</card-footer>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { NS, ACTIONS, GETTERS } from '#/store/modules/api';
import { MyButton, MyInput } from './components';
export default {
components: {
MyInput,
MyButton,
},
computed: mapGetters(NS, [
GETTERS.serverValue,
GETTERS.otherValue,
]),
methods: mapActions(NS, [
ACTIONS.updateState,
ACTIONS.saveState,
])
}
</script>
Since the Vuex store actions deal with the backend communication, our container here doesn't need to know about axios and the backend.
Okay, we can communicate between siblings via the parent using v-on events.
Parent
|- List of items // Sibling 1 - "List"
|- Details of selected item // Sibling 2 - "Details"
Let's assume that we want update Details component when we click some element in List.
In Parent:
Template:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Here:
v-on:select-item it's an event, that will be called in List component (see below);
setSelectedItem it's a Parent's method to update selectedModel;
JavaScript:
//...
data () {
return {
listModel: ['a', 'b']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item // Here we change the Detail's model
},
}
//...
In List:
Template:
<ul>
<li v-for="i in list"
:value="i"
#click="select(i, $event)">
<span v-text="i"></span>
</li>
</ul>
JavaScript:
//...
data () {
return {
selected: null
}
},
props: {
list: {
type: Array,
required: true
}
},
methods: {
select (item) {
this.selected = item
this.$emit('select-item', item) // Here we call the event we waiting for in "Parent"
},
}
//...
Here:
this.$emit('select-item', item) will send an item via select-item directly in the parent. And the parent will send it to the Details view.
How to handle communication between siblings depends on the situation. But first I want to emphasize that the global event bus approach is going away in Vue.js 3. See this RFC. Hence this answer.
Lowest Common Ancestor Pattern (or “LCA”)
For most cases, I recommend using the lowest common ancestor pattern (also known as “data down, events up”). This pattern is easy to read, implement, test, and debug. It also creates an elegant, simple data flow.
In essence, this means if two components need to communicate, put their shared state in the closest component that both share as an ancestor. Pass data from parent component to child component via props, and pass information from child to parent by emitting an event (example code below).
For example, one might have an email app: the address component needs to communicate data to the message body component (perhaps for pre-populating "Hello <name>"), so they use their closest shared ancestor (perhaps an email form component) to hold the addressee data.
LCA can be annoying if events and props need to pass through many "middlemen" components.
For more detail, I refer colleagues to this excellent blog post. (Ignore the fact that its examples use Ember, its concepts are applicable to many frameworks).
Data Container Pattern (e.g., Vuex)
For complex cases or situations where parent–child communication would involve too many middlemen, use Vuex or an equivalent data container technology.
Use namespaced modules when a single store becomes too complicated or disorganized. For example, it might be reasonable to create a separate namespace for a complex collection of components with many interconnections, such as a complex calendar.
Publish/Subscribe (Event Bus) Pattern
If the event bus (i.e. publish/subscribe) pattern makes more sense for your app (from an architecture standpoint), or you need to remove Vue.js's global event bus from an existing Vue.js app, the Vue.js core team now recommends using a third party library such as mitt. (See the RFC referenced in paragraph 1.).
Miscellaneous
Here's a small (perhaps overly simplistic) example of an LCA solution for sibling-to-sibling communication. This is a game called whack-a-mole.
In this game the player gets points when they "whack" a mole, which causes it to hide and then another mole appears in a random spot. To build this app, which contains "mole" components, one might think , “mole component N should tell mole component Y to appear after it is whacked”. But Vue.js discourages this method of component communication, since Vue.js apps (and html) are effectively tree data structures.
This is probably a good thing. A large/complex app, where nodes communicated to each-other without any centralized manager, might be very difficult to debug. Additionally, components that use LCA tend to exhibit low coupling and high reusability.
In this example, the game manager component passes mole visibility as a prop to mole child components. When a visible mole is "whacked" (clicked), it emits an event. The game manager component (the common ancenstor) receives the event and modifies its state. Vue.js automatically updates the props, so all of the mole components receive new visibility data.
Vue.component('whack-a-mole', {
data() {
return {
stateOfMoles: [true, false, false],
points: 0
}
},
template: `<div>WHACK - A - MOLE!<br/>
<a-mole :has-mole="stateOfMoles[0]" v-on:moleMashed="moleClicked(0)"/>
<a-mole :has-mole="stateOfMoles[1]" v-on:moleMashed="moleClicked(1)"/>
<a-mole :has-mole="stateOfMoles[2]" v-on:moleMashed="moleClicked(2)"/>
<p>Score: {{points}}</p>
</div>`,
methods: {
moleClicked(n) {
if(this.stateOfMoles[n]) {
this.points++;
this.stateOfMoles[n] = false;
this.stateOfMoles[Math.floor(Math.random() * 3)] = true;
}
}
}
})
Vue.component('a-mole', {
props: ['hasMole'],
template: `<button #click="$emit('moleMashed')">
<span class="mole-button" v-if="hasMole">🐿</span><span class="mole-button" v-if="!hasMole">🕳</span>
</button>`
})
var app = new Vue({
el: '#app',
data() {
return { name: 'Vue' }
}
})
.mole-button {
font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<whack-a-mole />
</div>
What I usually do if I want to "hack" the normal patterns of communication in Vue.js, specially now that .sync is deprecated, is to create a simple EventEmitter that handles communication between components. From one of my latest projects:
import {EventEmitter} from 'events'
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
With this Transmitter object you can then do, in any component:
import Transmitter from './Transmitter'
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit('update')
}
})
And to create a "receiving" component:
import Transmitter from './Transmitter'
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on('update', this.doThingOnUpdate)
}
})
Again, this is for really specific uses. Don't base your whole application on this pattern, use something like Vuex instead.
In my case i have a table with editable cells. I only want one cell to be editable at a time as the user clicks from one to another to edit the contents.
The solution is to use parent-child (props) and child-parent (event).
In the example below i'm looping over a dataset of 'rows' and using the rowIndex and cellIndex to create a unique (coordinate) identifier for each cell. When a cell is clicked an event is fired from the child element up to the parent telling the parent which coordinate has been clicked. The parent then sets the selectedCoord and passes this back down to the child components. So each child component knows its own coordinate and the selected coordinate. It can then decide whether to make itself editable or not.
<!-- PARENT COMPONENT -->
<template>
<table>
<tr v-for="(row, rowIndex) in rows">
<editable-cell
v-for="(cell, cellIndex) in row"
:key="cellIndex"
:cell-content="cell"
:coords="rowIndex+'-'+cellIndex"
:selected-coords="selectedCoords"
#select-coords="selectCoords"
></editable-cell>
</tr>
</table>
</template>
<script>
export default {
name: 'TableComponent'
data() {
return {
selectedCoords: '',
}
},
methods: {
selectCoords(coords) {
this.selectedCoords = coords;
},
},
</script>
<!-- CHILD COMPONENT -->
<template>
<td #click="toggleSelect">
<input v-if="coords===selectedCoords" type="text" :value="cellContent" />
<span v-else>{{ cellContent }}</span>
</td>
</template>
<script>
export default {
name: 'EditableCell',
props: {
cellContent: {
required: true
},
coords: {
type: String,
required: true
},
selectedCoords: {
type: String,
required: true
},
},
methods: {
toggleSelect() {
const arg = (this.coords === this.selectedCoords) ? '' : this.coords;
this.$emit('select-coords', arg);
},
}
};
</script>

VueJS Single File Component updating data

I'm really confused with how data works in single file components for VueJS. Within the file, say test.vue, as I understand, you would write out a script something like this:
export default {
name: 'Testapp',
data () {
return {
msg: 'sample message'
}
}
}
then elsewhere, say in a file called vuescript.js I would put something like following in and call it from an html file:
import Vue from 'vue'
import VApp from './test.vue'
var vueApp = new Vue({
el: '#app',
render: h => h(VApp)
})
Now how do I access that template object's data? What I'd like is to have code elsewhere that fetches data from a server and can be shared across multiple components, so I'd have a portion of data that is common across and updates from a single repository, but also I'd be able to have unshared data residing within a component for certain things like settings and other meta data.
BLUF: I'm kind of stuck after looking around a bit on how data is accessed / is handled within Vue single file components.
Data inside components should only be accessed outside of themselves in 2 ways. Either an event propagated the data upwards to a parent which can then decide if it needs to be passed to another component as a prop. Or it is stored in Vuex and accessed through getters and mutations.
Links
Component Props
Events
Vuex Example
If you want your data property to be shared by multiple components, you can use mixins.
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
https://v2.vuejs.org/v2/guide/mixins.html

Communication between sibling components in Vue.js 2.0

Overview
In Vue.js 2.x, model.sync will be deprecated.
So, what is a proper way to communicate between sibling components in Vue.js 2.x?
Background
As I understand Vue.js 2.x, the preferred method for sibling communication is to use a store or an event bus.
According to Evan (creator of Vue.js):
It's also worth mentioning "passing data between components" is
generally a bad idea, because in the end the data flow becomes
untrackable and very hard to debug.
If a piece of data needs to be shared by multiple components, prefer
global stores or Vuex.
[Link to discussion]
And:
.once and .sync are deprecated. Props are now always one-way down. To
produce side effects in the parent scope, a component needs to
explicitly emit an event instead of relying on implicit binding.
So, Evan suggests using $emit() and $on().
Concerns
What worries me is:
Each store and event has a global visibility (correct me if I'm wrong);
It's too wasteful to create a new store for each minor communication;
What I want is to some scope events or stores visibility for siblings components. (Or perhaps I didn't understand the above idea.)
Question
So, what is the correct way to communicate between sibling components?
You can even make it shorter and use the root Vue instance as the global Event Hub:
Component 1:
this.$root.$emit('eventing', data);
Component 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
With Vue.js 2.0, I'm using the eventHub mechanism as demonstrated in the documentation.
Define centralized event hub.
const eventHub = new Vue() // Single event hub
// Distribute to components using global mixin
Vue.mixin({
data: function () {
return {
eventHub: eventHub
}
}
})
Now in your component you can emit events with
this.eventHub.$emit('update', data)
And to listen you do
this.eventHub.$on('update', data => {
// do your thing
})
Update
Please see the answer by alex, which describes a simpler solution.
Disclaimer: this answer was written a long time ago and it may not reflect latest Vue development or trends. Take everything in this answer with a grain of salt and please comment if you find anything that's outdated, no longer valid, or unhelpful.
State scopes
When designing a Vue application (or in fact, any component based application), there are different types of data that depend on which concerns we're dealing with and each has its own preferred communication channels.
Global state: may include the logged in user, the current theme, etc.
Local state: form attributes, disabled button state, etc.
Note that part of the global state might end up in the local state at some point, and it could be passed down to child components as any other local state would, either in full or diluted to match the use-case.
Communication channels
A channel is a loose term I'll be using to refer to concrete implementations to exchange data around a Vue app.
Each implementation addresses a specific communication channel, which includes:
Global state
Parent-child
Child-parent
Siblings
Different concerns relate to different communication channels.
Props: Direct Parent-Child
The simplest communication channel in Vue for one-way data binding.
Events: Direct Child-Parent
Important notice: $on and $once were removed in Vue version 3.
$emit and v-on event listeners. The simplest communication channel for direct Child-Parent communication. Events enable 2-way data binding.
Provide/Inject: Global or distant local state
Added in Vue 2.2+, and really similar to React's context API, this could be used as a viable replacement to an event bus.
At any point within the components tree could a component provide some data, which any child down the line could access through the inject component's property.
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length)
}
}
})
app.component('todo-list-statistics', {
inject: ['todoLength'],
created() {
console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
}
})
This could be used to provide global state at the root of the app, or localized state within a subset of the tree.
Centralized store (Global state)
Note: Vuex 5 is going to be Pinia apparently. Stay tuned. (Tweet)
Vuex is a state management pattern + library for Vue.js applications.
It serves as a centralized store for all the components in an
application, with rules ensuring that the state can only be mutated in
a predictable fashion.
And now you ask:
[S]hould I create vuex store for each minor communication?
It really shines when dealing with global state, which includes but is not limited to:
data received from a backend,
global UI state like a theme,
any data persistence layer, e.g. saving to a backend or interfacing with local storage,
toast messages or notifications,
etc.
So your components can really focus on the things they're meant to be, managing user interfaces, while the global store can manage/use general business logic and offer a clear API through getters and actions.
It doesn't mean that you can't use it for component logic, but I would personally scope that logic to a namespaced Vuex module with only the necessary global UI state.
To avoid dealing with a big mess of everything in a global state, see the Application structure recommandations.
Refs and methods: Edge cases
Despite the existence of props and events, sometimes you might still
need to directly access a child component in JavaScript.
It is only meant as an escape hatch for direct child manipulation -
you should avoid accessing $refs from within templates or computed properties.
If you find yourself using refs and child methods quite often, it's probably time to lift the state up or consider the other ways described here or in the other answers.
$parent: Edge cases
Similar to $root, the $parent property can be used to access the
parent instance from a child. This can be tempting to reach for as a
lazy alternative to passing data with a prop.
In most cases, reaching into the parent makes your application more
difficult to debug and understand, especially if you mutate data in
the parent. When looking at that component later, it will be very
difficult to figure out where that mutation came from.
You could in fact navigate the whole tree structure using $parent, $ref or $root, but it would be akin to having everything global and likely become unmaintainable spaghetti.
Event bus: Global/distant local state
See #AlexMA's answer for up-to-date information about the event bus pattern.
This was the pattern in the past to pass props all over the place from far up down to deeply nested children components, with almost no other components needing these in between. Use sparingly for carefully selected data.
Be careful: Subsequent creation of components that are binding themselves to the event bus will be bound more than once--leading to multiple handlers triggered and leaks. I personally never felt the need for an event bus in all the single page apps I've designed in the past.
The following demonstrates how a simple mistake leads to a leak where the Item component still triggers even if removed from the DOM.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<button type="button" #click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
Remember to remove listeners in the destroyed lifecycle hook.
Component types
Disclaimer: the following "containers" versus "presentational" components is just one way to structure a project and there are now multiple alternatives, like the new Composition API that could effectively replace the "app specific containers" I'm describing below.
To orchestrates all these communications, to ease re-usability and testing, we could think of components as two different types.
App specific containers
Generic/presentational components
Again, it doesn't mean that a generic component should be reused or that an app specific container can't be reused, but they have different responsibilities.
App specific containers
Note: see the new Composition API as an alternative to these containers.
These are just simple Vue component that wraps other Vue components (generic or other app specific containers). This is where the Vuex store communication should happen and this container should communicate through other simpler means like props and event listeners.
These containers could even have no native DOM elements at all and let the generic components deal with the templating and user interactions.
scope somehow events or stores visibility for siblings components
This is where the scoping happens. Most components don't know about the store and this component should (mostly) use one namespaced store module with a limited set of getters and actions applied with the provided Vuex binding helpers.
Generic/presentational components
These should receive their data from props, make changes on their own local data, and emit simple events. Most of the time, they should not know a Vuex store exists at all.
They could also be called containers as their sole responsibility could be to dispatch to other UI components.
Sibling communication
So, after all this, how should we communicate between two sibling components?
It's easier to understand with an example: say we have an input box and its data should be shared across the app (siblings at different places in the tree) and persisted with a backend.
❌ Mixing concerns
Starting with the worst case scenario, our component would mix presentation and business logic.
// MyInput.vue
<template>
<div class="my-input">
<label>Data</label>
<input type="text"
:value="value"
:input="onChange($event.target.value)">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
value: "",
};
},
mounted() {
this.$root.$on('sync', data => {
this.value = data.myServerValue;
});
},
methods: {
onChange(value) {
this.value = value;
axios.post('http://example.com/api/update', {
myServerValue: value
});
}
}
}
</script>
While it might look fine for a simple app, it comes with a lot of drawbacks:
Explicitly uses the global axios instance
Hard-coded API inside the UI
Tightly coupled to the root component (event bus pattern)
Harder to do unit tests
✅ Separation of concerns
To separate these two concerns, we should wrap our component in an app specific container and keep the presentation logic into our generic input component.
With the following pattern, we can:
Easily test each concern with unit tests
Change the API without impacting components at all
Configure HTTP communications however you'd like (axios, fetch, adding middlewares, tests, etc)
Reuse the input component anywhere (reduced coupling)
React to state changes from anywhere in the app through the global store bindings
etc.
Our input component is now reusable and doesn't know about the backend nor the siblings.
// MyInput.vue
// the template is the same as above
<script>
export default {
props: {
initial: {
type: String,
default: ""
}
},
data() {
return {
value: this.initial,
};
},
methods: {
onChange(value) {
this.value = value;
this.$emit('change', value);
}
}
}
</script>
Our app specific container can now be the bridge between the business logic and the presentation communication.
// MyAppCard.vue
<template>
<div class="container">
<card-body>
<my-input :initial="serverValue" #change="updateState"></my-input>
<my-input :initial="otherValue" #change="updateState"></my-input>
</card-body>
<card-footer>
<my-button :disabled="!serverValue || !otherValue"
#click="saveState"></my-button>
</card-footer>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { NS, ACTIONS, GETTERS } from '#/store/modules/api';
import { MyButton, MyInput } from './components';
export default {
components: {
MyInput,
MyButton,
},
computed: mapGetters(NS, [
GETTERS.serverValue,
GETTERS.otherValue,
]),
methods: mapActions(NS, [
ACTIONS.updateState,
ACTIONS.saveState,
])
}
</script>
Since the Vuex store actions deal with the backend communication, our container here doesn't need to know about axios and the backend.
Okay, we can communicate between siblings via the parent using v-on events.
Parent
|- List of items // Sibling 1 - "List"
|- Details of selected item // Sibling 2 - "Details"
Let's assume that we want update Details component when we click some element in List.
In Parent:
Template:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Here:
v-on:select-item it's an event, that will be called in List component (see below);
setSelectedItem it's a Parent's method to update selectedModel;
JavaScript:
//...
data () {
return {
listModel: ['a', 'b']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item // Here we change the Detail's model
},
}
//...
In List:
Template:
<ul>
<li v-for="i in list"
:value="i"
#click="select(i, $event)">
<span v-text="i"></span>
</li>
</ul>
JavaScript:
//...
data () {
return {
selected: null
}
},
props: {
list: {
type: Array,
required: true
}
},
methods: {
select (item) {
this.selected = item
this.$emit('select-item', item) // Here we call the event we waiting for in "Parent"
},
}
//...
Here:
this.$emit('select-item', item) will send an item via select-item directly in the parent. And the parent will send it to the Details view.
How to handle communication between siblings depends on the situation. But first I want to emphasize that the global event bus approach is going away in Vue.js 3. See this RFC. Hence this answer.
Lowest Common Ancestor Pattern (or “LCA”)
For most cases, I recommend using the lowest common ancestor pattern (also known as “data down, events up”). This pattern is easy to read, implement, test, and debug. It also creates an elegant, simple data flow.
In essence, this means if two components need to communicate, put their shared state in the closest component that both share as an ancestor. Pass data from parent component to child component via props, and pass information from child to parent by emitting an event (example code below).
For example, one might have an email app: the address component needs to communicate data to the message body component (perhaps for pre-populating "Hello <name>"), so they use their closest shared ancestor (perhaps an email form component) to hold the addressee data.
LCA can be annoying if events and props need to pass through many "middlemen" components.
For more detail, I refer colleagues to this excellent blog post. (Ignore the fact that its examples use Ember, its concepts are applicable to many frameworks).
Data Container Pattern (e.g., Vuex)
For complex cases or situations where parent–child communication would involve too many middlemen, use Vuex or an equivalent data container technology.
Use namespaced modules when a single store becomes too complicated or disorganized. For example, it might be reasonable to create a separate namespace for a complex collection of components with many interconnections, such as a complex calendar.
Publish/Subscribe (Event Bus) Pattern
If the event bus (i.e. publish/subscribe) pattern makes more sense for your app (from an architecture standpoint), or you need to remove Vue.js's global event bus from an existing Vue.js app, the Vue.js core team now recommends using a third party library such as mitt. (See the RFC referenced in paragraph 1.).
Miscellaneous
Here's a small (perhaps overly simplistic) example of an LCA solution for sibling-to-sibling communication. This is a game called whack-a-mole.
In this game the player gets points when they "whack" a mole, which causes it to hide and then another mole appears in a random spot. To build this app, which contains "mole" components, one might think , “mole component N should tell mole component Y to appear after it is whacked”. But Vue.js discourages this method of component communication, since Vue.js apps (and html) are effectively tree data structures.
This is probably a good thing. A large/complex app, where nodes communicated to each-other without any centralized manager, might be very difficult to debug. Additionally, components that use LCA tend to exhibit low coupling and high reusability.
In this example, the game manager component passes mole visibility as a prop to mole child components. When a visible mole is "whacked" (clicked), it emits an event. The game manager component (the common ancenstor) receives the event and modifies its state. Vue.js automatically updates the props, so all of the mole components receive new visibility data.
Vue.component('whack-a-mole', {
data() {
return {
stateOfMoles: [true, false, false],
points: 0
}
},
template: `<div>WHACK - A - MOLE!<br/>
<a-mole :has-mole="stateOfMoles[0]" v-on:moleMashed="moleClicked(0)"/>
<a-mole :has-mole="stateOfMoles[1]" v-on:moleMashed="moleClicked(1)"/>
<a-mole :has-mole="stateOfMoles[2]" v-on:moleMashed="moleClicked(2)"/>
<p>Score: {{points}}</p>
</div>`,
methods: {
moleClicked(n) {
if(this.stateOfMoles[n]) {
this.points++;
this.stateOfMoles[n] = false;
this.stateOfMoles[Math.floor(Math.random() * 3)] = true;
}
}
}
})
Vue.component('a-mole', {
props: ['hasMole'],
template: `<button #click="$emit('moleMashed')">
<span class="mole-button" v-if="hasMole">🐿</span><span class="mole-button" v-if="!hasMole">🕳</span>
</button>`
})
var app = new Vue({
el: '#app',
data() {
return { name: 'Vue' }
}
})
.mole-button {
font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<whack-a-mole />
</div>
What I usually do if I want to "hack" the normal patterns of communication in Vue.js, specially now that .sync is deprecated, is to create a simple EventEmitter that handles communication between components. From one of my latest projects:
import {EventEmitter} from 'events'
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
With this Transmitter object you can then do, in any component:
import Transmitter from './Transmitter'
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit('update')
}
})
And to create a "receiving" component:
import Transmitter from './Transmitter'
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on('update', this.doThingOnUpdate)
}
})
Again, this is for really specific uses. Don't base your whole application on this pattern, use something like Vuex instead.
In my case i have a table with editable cells. I only want one cell to be editable at a time as the user clicks from one to another to edit the contents.
The solution is to use parent-child (props) and child-parent (event).
In the example below i'm looping over a dataset of 'rows' and using the rowIndex and cellIndex to create a unique (coordinate) identifier for each cell. When a cell is clicked an event is fired from the child element up to the parent telling the parent which coordinate has been clicked. The parent then sets the selectedCoord and passes this back down to the child components. So each child component knows its own coordinate and the selected coordinate. It can then decide whether to make itself editable or not.
<!-- PARENT COMPONENT -->
<template>
<table>
<tr v-for="(row, rowIndex) in rows">
<editable-cell
v-for="(cell, cellIndex) in row"
:key="cellIndex"
:cell-content="cell"
:coords="rowIndex+'-'+cellIndex"
:selected-coords="selectedCoords"
#select-coords="selectCoords"
></editable-cell>
</tr>
</table>
</template>
<script>
export default {
name: 'TableComponent'
data() {
return {
selectedCoords: '',
}
},
methods: {
selectCoords(coords) {
this.selectedCoords = coords;
},
},
</script>
<!-- CHILD COMPONENT -->
<template>
<td #click="toggleSelect">
<input v-if="coords===selectedCoords" type="text" :value="cellContent" />
<span v-else>{{ cellContent }}</span>
</td>
</template>
<script>
export default {
name: 'EditableCell',
props: {
cellContent: {
required: true
},
coords: {
type: String,
required: true
},
selectedCoords: {
type: String,
required: true
},
},
methods: {
toggleSelect() {
const arg = (this.coords === this.selectedCoords) ? '' : this.coords;
this.$emit('select-coords', arg);
},
}
};
</script>

Categories

Resources