Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.
Related
I am new in React.js, and I have an issue with parent component state updating. I attached whole code so that don't miss something that can be important.
I need my 'Reset' button to reset parent component's state
HERE you can see what the issue is.
You should open 'Events' in Navigator menu and try to select categories and then to reset them.
parent component: components/events/filter-bar.js
chils component: components/shared/dropdown/category.js
So, you have categories in FilterBar and Category components state - which are not in sync. Since FilterBar also recieves categories as a prop - that indicates that you don't have a single source of truth for categories. There are at least three places where you are keeping categories in different components states. That is very bad thing and leads to bugs and hard to maintain state. Solution is having categories state in just one place and than passing it down to components which need them (together with methods for updating categories).
Anyway, if reseting only dropdown categories is what you want this is what you can do:
create a new method in FilterBar, name it whatewer you want and pass it as a prop to Categories component. This method will accept just one argument - array of categories. This method will update FilterBar state.categories.
in Categories component, remove state.selected and saveSelected method. In places where you are using saveSelected replace it with prop (function/method) passed from FilterBar.
update
This question actually has nothing to do with component lifecycles or updating parent state.
Here is a thing: you can't control the state you don't own (which is the case with 3rd party components). Some lib authors provide methods for setting initial state or reset, but that is not the case with component library you are using.
In your case, best you can do is to unmount dropdowns ad then mounts them again. It's something like refreshing just the part of the page.
Do this:
add visible: true to the FilterBar state
add onReset method to the same component:
onReset = () => {
this.setState(
state => ({ visible: false }),
() => {
this.setState({ visible: true });
}
);
};
update reset button: onClick={this.onReset}
conditionally render ButtonToolbar:
return this.state.visible ? <ButtonToolbar /> : null // have shortened code just for demonstration purpose
started with VueJs for the first time
yesterday and now I'm stuck..
I have a parent component who has child items that also has a child inside them (I call them grandchildren). I want to fetch data from all the grandchildren when i click a button in the parent but i can't figure out how.
In my mind a want to call an event from parent to to all the grandchildrens that they should store their data to vuex store. Is this possible somehow or is there another way to do this?
// Data
blocks = [
{
id: 1,
type: 'HeadingBlock',
title: 'Hello',
color: 'blue'
},
{
id: 2,
type: 'ImageBlock',
image_id: 2
}
];
// App.js
<ContentBlocks :blocks="blocks" / >
// ContentBlock.vue
<ContentBlockItem v-for="(block, index) in blocks" :component="block.type" ... />
// ContentBlockItem.vue
<component :is="component" :block="block" /> // Grandchild
// component aka the grandchild (eg. HeadingBlock.vue)
data() {
return {
title: 'Hello - I want save the changed data for this heading',
color: 'blue'
}
}
So, the only call to action happens in the parent by a "save"-button. And i want as little logic in grandchildren as possible (to make it easy to create new ones, like a "ParagraphBlock").
Thx in advance
It is possible to emit() a global Event that all your components subscribe to - however it seems rather impractical. (e.g. this.$root.emit('saveData') in parent; in all children to listen to it: this.$root.on('saveData'))
A more practical approach is to store all your component data in the store in the first place. And have each component retrieve its state from the store. (e.g. in a computed property: title() { return this.$store.myComponent.title }.
The trick here is obviously to set all your store-data correctly (e.g. with componentIDs to match it correctly). To do this you need to be aware, that vuex does not support maps or sets. Also, you have to set each property/element individually - you cannot set nested structures in one go bu thave to do it recursively. Hereby Arrays have to be filled with native array methods (push(), splice()) and Object properties have to be set with Vue.set(object, key, value).
For accessing data between parent and child component you can use one of the best features of vue is vuex store. It's really helpful when you want to pass data to child component and update that data in child and again pass back to parent without the use of props and event emit.
Here is a link you can follow for your web application
https://medium.com/dailyjs/mastering-vuex-zero-to-hero-e0ca1f421d45
https://medium.com/vue-mastery/vuex-explained-visually-f17c8c76d6c4
I hope this will help you.
I'm pretty new to react and have been working on this new page for work. Basically, there's a panel with filter options which lets you filter objects by color. Everything works but I'm noticing the entire filter panel flickers when you select a filter.
Here are the areas functions in the filter component I think bear directly on the filter and then the parent component they're inserted into. When I had originally written this, the filter component was also calling re render but I've since refactored so that the parent handles all of that - it was causing other problems with the page's pagination functionality. naturally. and I think that's kind of my problem. the whole thing is getting passed in then rerendered. but I have no idea how to fix it. or what's best.
checks whether previous props are different from props coming in from parent and if so, creates copy of new state, calls a render method with those options. at this point, still in child component.
componentDidUpdate(prevProps, prevState) {
if (prevState.selectedColorKeys.length !== this.state.selectedColorKeys.length ||
prevState.hasMatchingInvitation !== this.state.hasMatchingInvitation) {
const options = Object.assign({}, {
hasMatchingInvitation: this.state.hasMatchingInvitation,
selectedColorKeys: this.state.selectedColorKeys
});
this.props.onFilterChange(options);
}
}
handles active classes and blocks user from selecting same filter twice
isColorSelected(color) {
return this.state.selectedColorKeys.indexOf(color) > -1;
}
calls to remove filter with color name so users can deselect with same filter button or if its a new filter, sets state by adding the color to the array of selected color keys
filterByColor(color) {
if (this.isColorSelected(color.color_name)) {
this.removeFilter(color.color_name);
return;
}
this.setState({
selectedColorKeys:
this.state.selectedColorKeys.concat([color.color_name])
});
}
creating the color panel itself
// color panel
colorOptions.map(color => (
colorPicker.push(
(<li className={['color-filter', this.isColorSelected(color.color_name) ? 'active' : null].join(' ')} key={color.key} ><span className={color.key} onClick={() => { this.filterByColor(color); }} /></li>)
)
));
parent component
callback referencing the filter child with the onFilterChange function
<ThemesFilter onFilterChange={this.onFilterChange} />
onFilterChange(filters) {
const { filterThemes, loadThemes, unloadThemes } = this.props;
unloadThemes();
this.setState({
filterOptions: filters,
offset: 0
}, () => {
filterThemes(this.state.filterOptions.selectedColorKeys, this.state.filterOptions.hasMatchingInvitation);
loadThemes(this.state.offset);
});
}
when I place break points, the general flow seems to be :
filterByColor is triggered in event handler passing in that color
active classes are added to the color, a filter tag for that color is generated and appended
componentDidMount takes in the previous props/state and compares it to the new props/state. if they don't match, i.e state has changed, it creates a copy of that object, assigning the new states of what's changed. passes that as props to onFilterChange, a function in the parent, with those options.
onFilterChange takes those options, calls the action method for getting new themes (the filtering actually happens in the backend, all I really ever need to do is update the page) and passes those forward. its also setting offset to 0, that's for the page's pagination functionality.
It looks like the problem might be around the componentDidUpdate function which, after setting breakpoints and watching it go through the steps from filterByColor to componentDidMount, that componentDidMount loops through twice, checking again if the colorIsSelected, and throughout all that the color panel pauses to re-render and you get a flicker.
Is it possible creating the copy is causing it? since it's being treated, essentially, as a new object that isColorSelected feels necessary to double check? any help you guys have would be much appreciated, this shit is so far over my head I can't see the sun.
Can you change
componentDidUpdate(prevProps, prevState)
with
componentWillUpdate(nextProps, nextState)
How could a child component pass its value to the parent component? Here is my child component:
Javascript:
new Vue({
el: '#table-list',
data: {
tableList: ['Empty!'],
tableSelected: ""
},
methods: {
getTableList() {
axios
.get('/tables')
.then(tableList => {
this.tableList = tableList.data;
})
.catch(e => console.warn('Failed to fetch table list'));
},
selectTable(table) {
this.tableSelected = table;
}
},
mounted() {
this.getTableList();
}
});
HTML:
<div id="table-list">
<p v-for="table in tableList">
<i class="fa fa-table" aria-hidden="true"></i>
<span class="text-primary" v-on:click="selectTable(table)"> {{ table }} </span>
</p>
</div>
When on click, selectTable is called, I want to show the value in its parent component? i.e I need to pass tableSelected property to the parent component. How could I do this?
You should use vue components, specifically events mechanism for what you want to archive.
Props are for pass data from parent to a child components, and events to send messages from child component to parent.
We have learned that the parent can pass data down to the child using props, but how do we communicate back to the parent when something happens? This is where Vue’s custom event system comes in.
Please see this fiddle https://jsfiddle.net/AldoRomo88/sLo1zx5b/
I have changed your selectTable method to emit a custom event
selectTable: function(table) {
this.$emit('item-changed',table);
}
And in your parent component you just need to listen for that event
<div>
{{selectedItem}}
</div>
<table-list #item-changed="newValue => selectedItem = newValue " ></table-list>
Let me know if you need more clarification.
Here is the page that explains how children emit events to listening parents.
Here is the page on managing state.
Remember what you're aiming for, with VUE, is MVVM. You want all your state in a store, where each item of state is stored once, regardless of how many times it's referenced, and how many ways it can be updated.
Your tableSelected is an item of state. You can pass state changes up the chain if you need to, so long as they finish in a store, not in a component or a vue. But you can keep it simple: make tableSelected a property in your store, and declare it directly in the data element of components that need it. If you want to be rigorous, put a changeTableSelected() method on the store.
You need to start worrying about props and events if one component will have many instances, or if a component knows nothing about the page on which it will appear. Until that time, I would prefer using data and the store.
This is what I have:
<div id='vnav-container'>
<input type="text" v-model="searchTerm" v-on:keyup="search" class="vnav-input">
<menu :items="menu"></menu>
</div>
The outer component contains a search-input and a menu component.
When the user performs a search on the outer component, I need to call a method on the menu component, or emit an event, or whatever, as long as I can communicate to the menu component saying it should filter itself based on the new criteria.
I've read somewhere that calling methods on child components is discouraged and that I should use events. I'm looking at the docs right now, but I can only see an example of a child talking to a parent, not the other way around.
How can I communicate to the menu component as the search criteria changes?
EDIT
According to some blog posts, there used to be a $broadcast method intended to talk to child components but the documentation about that just vanished. This used to be the URL: http://vuejs.org/api/#vm-broadcast
The convention is "props down, events up". Data flows from parents to child components via props, so you could add a prop to the menu, maybe:
<menu :items="menu" :searchTerm="searchTerm"></menu>
The filtering system (I'm guessing it's a computed?) would be based on searchTerm, and would update whenever it changed.
When a system of components becomes large, passing the data through many layers of components can be cumbersome, and some sort of central store is generally used.
Yes, $broadcast was deprecated in 2.x. See the Migration guide for some ideas on replacing the functionality (which includes event hubs or Vuex).
Or you can create the kind of simple store for that.
First off, let's create the new file called searchStore.js it would just VanillaJS Object
export default {
searchStore: {
searchTerm: ''
}
}
And then in files where you are using this store you have to import it
import Store from '../storedir/searchStore'
And then in your component, where you want to filter data, you should, create new data object
data() {
return {
shared: Store.searchStore
}
}
About methods - you could put method in your store, like this
doFilter(param) {
// Do some logic here
}
And then again in your component, you can call it like this
methods: {
search() {
Store.doFilter(param)
}
}
And you are right $broadcast and $dispatch are deprecated in VueJS 2.0