On component1.vue I have
export default {
data () {
return {
editItemNumber: null,
editBreakdownNumber: null
}
}
On component2.vue I have a table populated by an array
On that table is an edit button. Among the item in that table is itemNumber value. Which I need to assign from that particular row clicked to the editItemNumber on component1.vue
<b-table show-empty bordered striped hover :items="itemTableList" :fields="fields">
<template slot="actions" scope="row">
<b-btn variant='success' size="sm" #click.stop="edit(row.item,row.index,$event.target)">Edit</b-btn>
</template>
</b-table>
As you see above originally all of this was on one component and I was just calling to an edit function which repopulated the v-models with that rows contents. But now with everything split among components I don't know how to edit this to do what I've been tasked with.
I've never used JavaScript, vue or much of anything beyond basic HTML. I'm a .NET dev who's been tasked with helping out on some web based work and I'm floundering. So any help is appreciated.
The preferred way to move data between components is with events.
Normally you use props to pass data from a parent component to a child, and events to pass from a child up to a parent.
So the editmethod of C2 can be something like
edit(item, index, target) {
const payload = {
item,
index,
target
};
this.$emit('edit', payload);
}
Then you just have to listen to that event in C1. Notice the #edit attribute: that means when the edit event is fired from component-one, run my "edit" method.
<template>
<div>
<p>{{ editItemNumber }}</p>
<component-two #edit="edit" />
</div>
<script>
export default {
data () {
return {
editItemNumber: null,
editBreakdownNumber: null
};
},
methods: {
edit(eventPayload) {
this.editItemNumber = eventPayload.item.editItemNumber
}
}
</script>
If you both C1 and C2 are children of the same parent P the idea is the same, except C1 can't listen directly to C2. Instead P will listen to the edit event and pass the needed changes down to C1 through its props.
The docs on components are really good, pay special attention to the sections on props and custom events.
https://v2.vuejs.org/v2/guide/components.html
Related
In Vue 2 I used to be able to access a property on component children (rendered inside a v-for loop using this.$refs and a dynamically-assigned :ref).
The same code in Vue 3 fails, and when I log out this.$refs the object is empty.
Here I'm wanting to access an 'isOrderable' property on all children. The problem appears to be with :ref="product.id" being a variable. If I change it to ref="foobar" then I do get the last child in this.$refs.foobar. But it vue2 me an array back containing all children components.
<script>
import productItem from "./Product.vue";
export default {
props: ["products"],
components: {
'product-item': productItem
}
methods: {
addAllProducts() {
const orderable = this.products.filter((p) => this.$refs[p.id][0].isOrderable);
...
}
}
}
</script>
<template>
<form>
<div v-for="product in products" :key="product.id">
<product-item :product="product" :ref="product.id" />
</div>
<button #click="addAllProducts">Add All</button>
</form>
</template>
Obviously something changed in vue3 but I can't find any info about it. There's plenty of info on this.$refs, and but it all has to do with accessing refs from the composition API.
Any help appreciated.
In vue 3 they change how refs work with arrays, now you need to pass a function and have a state on your data to keep track of your refs https://v3-migration.vuejs.org/breaking-changes/array-refs.html#frontmatter-title.
I don't know how your code is structured but maybe there is a better solution to your problem than using refs, if the logic that toggles if a product-item is orderable lives inside the product-item component you can have an event that emits when the orderable value is changed an update an array of orderableProducts with the id of each product, you can even use that in a v-model with the multiple v-models options of vue3. in that way you don't need to hold a reference of the dom just to filter by the ones that are orderable.
I wrote a react component in render props way,it will call children function with 3 react component object ( not sure the name exactly, the variable generated by executing jsx (<div>...</div>) );
<PaginatedTable> Usage example:
<PaginationTable data={data} ...otherprops>
{({ SearchBar, Table, PaginationBar })=>
(<div>
{SearchBar}
{Table}
{PaginationBar}
</div>)
}
</PaginationTable>
with render props, I'm so glad that I can custom these 3 child component object very easily such as rearrange order or adding custom elements between these three.
{({ SearchBar, Table, PaginationBar })=>
(<div>
{PaginationBar}
<h1> my custom search bar text </h1>
{SearchBar}
{Table}
</div>)
}
But now I wish more than arrange order inside , I wish I can move {SearchBar} out of to the same layer of 's sibling 's children such as this picture.
working demo: https://codesandbox.io/s/23q6vlywy
I thought this may be anti-pattern to the unidirectional data flow of React.
Extract {SearchBar} to another independent component then use it as <SearchBar ... /> inside of <ToolBarArea /> is what I learnd from React Doc.
But in this way, I have to do "lifting state up" and write similar function and states already had in <PaginationTable /> like below **text** parts are functions and states already had in <PaginationTable />
class ToolBarArea extends Component{
render(){
return(
<div>
// text
<SearchBar onChange={**this.props.onSearchBarChange**} />
//.... other text or elements
</div>);
}
}
class ContainerArea extends Component {
state={
**searchBarText:'',**
tableData : [{...}, {...}]
}
**onSearchBarTextChange = (event)=>{
this.setState({ searchBarText: event.target.value });
}
filterdTableData = ()=> this.state.tableData.filter(d=>d.name.includes(this.state.searchBarText);
**
}
I really hope there is a way I can simply move the variable {SearchBar} in the render props function out to without knowing is in the parent or parent's sibling or anywhere in the DOM tree.
such as
<ToolBarArea>
{SearchBar} // SearchBar from <PaginationTable />
</ToolBarArea>
Is there a way to reuseonSearchBarTextChange and filtedTableData functions and these **text** codes I already wrote in <PaginationTable /> ?
I believe you hit the nail on the head when you referred to lifting state. If you already wrote a similar function then your best option may be to 'abstract' that function so that it applies to both use cases. You could use a simple flag to differentiate the unique execution each needs. Then finally pass the function down to both components.
If you're adamant about avoiding this approach you could technically get around it by using event listeners to handle data transfer or watch variables in the window but this is for sure an anti-pattern.
I'm very confused about how to properly tie components together.
I have two components registered globally:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
showModal = false;
}
});
Vue.component('product-create-modal-component', {
template: '#create-modal-template'
});
In the parent's template I included the child component like this:
<template id="product-welcome-template">
<div class="welcome-wrapper">
<div class="purpose-title"><h1 class="welcome-text">Welcome to Product Hub</h1></div>
<div class="purpose-create-btn"><button ##click="showModal = true" class="btn btn-primary btn-success create-btn">Create New Product</button></div>
<product-create-modal-component v-if="showModal"></product-create-modal-component>
</div>
</template>
The problem is (one of them) is that my create-modal-component is always showing, regardless of the value of showModal, in fact i can put in v-if="1 === 2" it would still show.
I'm sure this is not the right way of registering parent / child components but I can't seem to find a proper example. Mostly what i see that the parent is the app instance and it has a child of 'child' component and then they can communicate.
I have a feeling that including the child component in the parent's template is bad practice as it makes the parent strongly coupled.
Any help would be appreciated, thank you!
You are having showModal as props to product-welcome-component, but you are trying to set it false in created, but you have to use this in created to access showModal, like following:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
this.showModal = false;
}
});
However you are saying product-create-modal-component shows even you do v-if="1 === 2", which should not be the case Can you create a fiddle of your case.
I'm creating a React app on Rails and have encountered a problem regarding key assignment to dynamic children.
Below is a pared-down copy of my code:
class Records extends React.Component {
render () {
var records =
this.props.data.map(function(record) {
return <div>
<Record key={record.id} data={record} />
</div>;
});
return (
<div>
{records}
</div>
);
}
}
class Record extends React.Component {
render () {
return (
<div>
<h1>Title: {this.props.data.title}</h1>
</div>
);
}
}
The code runs correctly except for the warning below that appears on the console:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `Records`.
I've followed the link contained within the warning to the React guide (http://facebook.github.io/react/docs/multiple-components.html#dynamic-children).
Though I feel I've implemented their recommendation that the key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array, I still receive this warning in the console.
Does anyone have any ideas why? I completely appreciate any help you might provide!
The problem is that you should add the key props to the outer div, not to your Record instance.
var records =
this.props.data.map(function(record) {
return <div key={record.id}>
<Record data={record} />
</div>;
});
In fact, you don't need the wrapping div at all.
The warning is there because you need to add the key prop to the repeated element, and in your code you have a list of divs, each containing a Record (not a list of Records).
React asks you to do so to be able to "track" (identify) these divs which would be exactly the same without a way to identify them. React needs this ids to efficiently track DOM mutations (e.g. reordering them, removing them, etc)
Then, if you use ES6 (transpiled with Babel or similar) along with JSX you can just to this:
var records = this.props.data.map(record => <Record data={record} key={record.id} />)
In your code sample, the child in the is the surrounding <div> element, not the Record component which has the key property.
Your resulting HTML structure looks like this
<div>
<div><h1 key="1">...</h1></div>
<div><h1 key="2">...</h1></div>
<div><h1 key="3">...</h1></div>
</div>
So indeed React is correct, the collection does not have a unique key for the top-level element.
Your two options are:
(A) Add the key property to the surrounding <div> such that it looks like this
<div>
<div key="1"><h1>...</h1></div>
<div key="2"><h1>...</h1></div>
<div key="3"><h1>...</h1></div>
</div>
var records =
this.props.data.map(function(record) {
return <div key={record.id}>
<Record data={record} />
</div>;
Or
(B) Get rid of the surrounding <div> altogether as it's not needed.
var records =
this.props.data.map(function(record) {
return <Record key={record.id} data={record} />;
Side-note: React expects your render call to only return a single top-level element, so you only need a container element if you want to have multiple sibling elements in a render call.
I've been hiding/showing react components by not rendering them, for example:
render: function() {
var partial;
if (this.state.currentPage === 'home') {
partial = <Home />;
} else if (this.state.currentPage === 'bio') {
partial = <Bio />;
} else {
partial = <h1>Not found</h1>
}
return (
<div>
<div>I am a menu that stays here</div>
Home Bio
{partial}
</div>
);
}
but just say that the <Bio/> component has lots of internal state. Everytime I recreate the component, it loses it's internal state, and resets to it's original state.
I know of course that I could store the data for it somewhere, and pass it in via props or just globally access it, but this data doesn't really need to live outside of the component. I could also hide/show components using CSS (display:none), but I'd prefer to hide/show them as above.
What's the best practice here?
EDIT: Maybe a better way to state the problem is to use an example:
Ignore React, and assume you were just using a desktop app that had a configuration dialog with a Tab component called A, which has 2 tabs, named 1 and 2.
Say that tab A.1 has an email text field and you fill in your email address. Then you click on Tab A.2 for a second, then click back to Tab A.1. What's happened? Your email address wouldn't be there anymore, it would've been reset to nothing because the internal state wasn't stored anywhere.
Internalizing the state works as suggested in one of the answers below, but only for the component and it's immediate children. If you had components arbitrarily nested in other components, say Tabs in Tabs in Tabs, the only way for them to keep their internal state around is to either externalize it somewhere, or use the display:none approach which actually keeps all the child components around at all times.
It just seems to me that this type of data isn't data you want dirtying up your app state... or even want to even have to think about. It seems like data you should be able to control at a parent component level, and choose to either keep or discard, without using the display:none approach and without concerning yourself with details on how it's stored.
One option would be to move the conditional inside the component itself:
Bio = React.createClass({
render: function() {
if(this.props.show) {
return <p>bio comp</p>
} else {
return null;
}
}
});
<Bio show={isBioPage} />
Whether this is "best practise" or not probably depends on the exact situation.
Unfortunately, style={{display: 'none'}} trick only works on normal DOM element, not React component. I have to wrap component inside a div. So I don't have to cascade the state to subcomponent.
<div className="content">
<div className={this.state.curTab == 'securities' ? 'active' : ''}>
<Securities />
</div>
<div className={this.state.curTab == 'plugins' ? 'active' : ''}>
<Plugins />
</div>
</div>
Looks like official documentation suggests hiding stateful children with style={{display: 'none'}}
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
I propose the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
Rationale
react/react-dom comes comes with 2 basic assumptions/ideas:
every UI is hierarchical naturally. This why we have the idea of components which wrap each other
react-dom mounts (physically) child component to its parent DOM node by default
The problem is that sometimes the second property isn't what you want
in your case. Sometimes you want to mount your component into
different physical DOM node and hold logical connection between
parent and child at the same time.
Canonical example is Tooltip-like component: at some point of
development process you could find that you need to add some
description for your UI element: it'll render in fixed layer and
should know its coordinates (which are that UI element coord or
mouse coords) and at the same time it needs information whether it
needs to be shown right now or not, its content and some context from
parent components. This example shows that sometimes logical hierarchy
isn't match with the physical DOM hierarchy.
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example to see the concrete example which is answer to your question (take a look at the "use" property):
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...