I have a React component called <SensorList /> that has many child <SensorItem />s (another React component). I want to be able to declare an onClick event on each <SensorItem /> from within <SensorList />. I have tried doing the following:
sensorSelected: function(sensor) {
console.log('Clicked!');
},
render: function() {
var nodes = this.state.sensors.map(function(sensor) {
return (
<SensorItem onClick={ this.sensorSelected } />
);
}.bind(this));
return (
<div className="sensor-list">
{ nodes }
</div>
);
}
Needless to say, I do not get any "Clicked!" coming up in my console. The React inspector in Chrome indicates that an onClick event is registered, with the above function body as it should be.
I conclude, therefore, that I can't register onClick events on the actual <SensorItem /> tags (I'm not sure why this is, however). How do I go about achieving this otherwise?
This depends on your SensorItem component's definition.
Because SensorItem isn't a native DOM element but, like you said, another React component, onClick as defined here is simply a property of that component. What you need to do is, inside of the SensorItem component pass the onClick prop to an DOM component's onClick event:
var SensorItem = React.createClass({
render: function() {
return (
<div className="SensorItem" onClick={this.props.onClick}>
...
</div>
);
}
});
Problem
The problem, as being explained in another answer, is that onClick on a <SensorItem> React component (contrary to native DOM element like <div> or <p>) is treated as passing of component property, and not of a DOM event handler. And as most likely your <SensorItem> component doesn't declare onClick property, that property value simply gets lost.
Solution
The most straightforward solution is to add onClick property explicitly on SensorItem component, then pass it to the root DOM element of that component:
function SensorItem({ prop1, prop2, onClick }) {
(...)
return (
<p onClick={onClick}>
(...)
</p>
);
}
But the solution that usually works best for me is to group all the undefined component's properties using object destructuring notation, then pass them all to the root DOM element within that component. This way you can pass onClick, onHover, className etc. without needing to define separate properties for each one of them:
function SensorItem({ prop1, prop2, ...rootDOMAttributes }) {
(...)
return (
<p {...rootDOMAttributes}>
(...)
</p>
);
}
No matter which of the two approaches you use, the handler will now work, as it'll now be attached to the root DOM element of SensorItem:
<SensorItem onClick={...} />
Related
I have two components, a parent and a child.
Parent Component
const Parent = () => {
return (
<>
<div className="container"></div>
<div>more content</div>
<Child/>
</>
)
}
Child Component
const Child = () => {
const importantFunctionMustBeInChild = () => {
//Does a bunch of stuff that can't be done in the parent due to a lot of state that doesn't make sense to have in the parent
}
return (
<>
<button onClick={importantFunctionMustBeInChild}>Important Button</button>
</>
)
}
The problem is that I have a button in the child component. This button renders conditionally based off many different state toggles and has functions that can't be in the parent component because it wouldn't make sense to put it there and would take a long time to move all the state and functions up.
The issue is I now need to have the button to where the container div is in the parent component. Is there any way this can be done?
Going by the logic, you can assign a value(string, object, array) to parent component's state in the child component, right? So why can you not assign a function to the state and run it on click in the parent?
Definetely you can! In your child, assign function as a reference to the parent's state and thats it!
useEffect(() => {
// assign, don't invoke the function yet!
setFunctionToExecute(() => importantFunctionMustBeInChild);
}, []);
Here's a complete working demo
in Vue3 I can do the following:
Parent Component:
<ChildComponent :props="{ id: 'the-id', class: 'the-class' }" />
ChildComponent:
<template>
<input v-bind="props" />
</template>
<script>
export default {
props: {
props: { type: Object }
}
}
</script>
This will result in HTML like this:
<input id="the-id" class="the-class" />
I'm still learning Vue and I was wondering if I could do the same thing with event listeners / handlers.
With reusable components I might need different event listeners / handlers, depending on where I use the component. In one form I might need only an #input="..." in the child component, in another form I might also need an #blur="..." or I might not need an event listener at all.
Is it possible to do something similar to this?
ParentComponent:
<ChildComponent :events="{ input: function() { alert('input!'); }, blur: function() { alert('blur!'); } } />
ChildComponent:
<template>
<input #events />
</template>
<script>
export default {
props: {
props: { type: Object }
}
}
</script>
Thank you ;)
You can also do something similar like you did with the props, by passing an object to v-on:
<input v-on="{ input: doThis, blur: doThat }"></button>
See here: https://v3.vuejs.org/api/directives.html#v-on
The root element of a component automatically inherits all non-prop attributes (including event listeners) applied to the component from the parent. So if <input> were really at the root in ChildComponent, you wouldn't need to declare anything, and you could simply apply attributes to ChildComponent as if it were the input itself:
<ChildComponent id="the-id" class="the-class" #blur="onBlur" />
demo 1
However, this will break as soon as you add another element to ChildComponent because <input> would no longer be the root. It also would be a problem if you wanted to switch the root element (e.g., a <label> that wraps the <input>).
To disable automatic attribute inheritance, allowing control of where to apply the inherited attributes, set inheritAttrs: false in component options. (This doesn't need to be disabled when there are multiple root nodes, as it only applies for single-root components.) Then manually v-bind the $attrs prop to any element within:
<template>
<label>My input: <input v-bind="$attrs"></label>
</template>
<script>
export default {
inheritAttrs: false,
}
</script>
demo 2
My recommendation would be to define the different events though Vue's Custom Events; The custom events would allow the users of the component to choose which event to subscribe to and handle it accordingly.
https://v2.vuejs.org/v2/guide/components-custom-events.html
I have set a static id to react element and onClick listener is set to the component, I pass the event object as the param to the listener function and event.target.id is an empty string, how do I get the id of the clicked element?
If I didn't call event.persist(), I am getting all values null, why is it so?
sendDisplayType = (event) => {
event.persist();
console.log(event);
}
<div id="mission" onClick={this.sendDisplayType} className="col l8 m6 left-align nopaddingleft white-space greentext">
<h5> <b>Our Mission</b></h5>
</div>
the event generated is not dom event, it is react event, you should set ref on the div tag to get access to its values including id.
If I didn't call event.persist(), I am getting all values null, why is it so?
All properties will be nullified after the event callback has been invoked. This is for performance reasons.Reference for event pooling
To get an id, what I usually do is just do a classic document.getElementById('id').
But if you really want to go the event way without calling a function on the event prop, what you can do is pass the id as data
so:
<div id="mission" data-id="mission" onClick={this.sendDisplayType} className="col l8 m6 left-align nopaddingleft white-space greentext">
<h5> <b>Our Mission</b></h5>
</div>
Then get it as:
sendDisplayType = (event) => {
event.persist();
console.log(event.target.getAttribute('data-id'));
}
I strongly recommend the code above compare on adding an arrow function like:
<div id="mission" onClick={() => this.sendDisplayType('mission')} />
I don't usually use the code above (calling a function on prop) because of optimization (so if you have a performance issue, optimize as much as possible) - React docs explaining this
I have a React component I built for a popup. PopupContent will receive a DOM element or another React component as a child.
class PopupContent extends React.Component {
render() {
return(
<div>
{React.cloneElement(this.props.children, {closePopup:this.props.closePopup})}
</div>
);
}
}
The closePopup prop sets a flag to show/hide the popup
closePopup(event){
event.preventDefault();
this.setState({
popupInView: false
})
}
The reason to pass closePopup to child is to close the popup from the child component.
This setup works well if the child is a custom React component:
<PopupContent>
<ContentOfThePopup />
</PopupContent>
But I get the Unknown Prop Warning if the child is a DOM element.
Warning: React does not recognize the closePopup prop on a DOM
element.
<PopupContent>
<div>Content Of The Popup </div>
</PopupContent>
I could use techniques explained here to distinguish between a DOM element and a React component. But I wanted to check with the community if there is a better way
what does this.props.children contain?
Shouldn't you be iterating over it?
render() {
return React
.Children
// this is the jsx version of cloneElenemnt,
// better to use in a render function
.map(Child => <Child.type ...Child.props ...this.props>)
}
Clone Element
React.Children.map
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
You should ensure that you are not accidentally forwarding props that were intended to be interpreted by the parent component.
Also you can try {...this.props} format to pass your data instead of using cloneElement(element, this.props)
I've ended up checking the type of the child and conditionally removing the prop closePopup
Following condition will be true if the child is a HTML DOM element.
typeof this.props.children.type === 'string
I have the following Vue.js components, which basically are supposed to have a radiobutton-like behaviour:
// Parent Component
<template>
<child-component
v-for="element in elements"
</child-component>
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: {
ChildComponent
},
props: {
elements: Array
},
methods: {
activate(e) {
for (let i of this.$children) {
i.active = false;
}
if (e < this.$children.length) {
this.$children[e].active = true;
}
}
}
}
</script>
and
// Child Component
<template>
{{active}}
</template>
<script>
export default {
props: {
active: Boolean
}
}
</script>
This works fine but only the parent can decide to activate one of the children (and thus deactivate all others).
I want however also be able to allow each child to activate itself (and by a magic property of its parent, deactivate all other siblings).
Obviously I do not want each child to know about its siblings and mess with their .active prop (super bad design).
I would rather not have a children communicate back up to the parent and call some method (still bad design as I could only reuse the child components in parents that have activate() method).
Instead I would like the parent to listen to changes to all children active props and take action when one of them changes. That way the parent entirely encapsulates the radio button behavior.
How can this interaction be implemented in Vue.js?
Take a look at two-way binding: http://vuejs.org/guide/components.html#Prop_Binding_Types
This allows you to sync a property's value in both directions, meaning the parent or the child has access to change the variable. Then you can watch for changes on the parent and update accordingly.
I think a better option would be to create a RadioSet component, which then would house a number of radio buttons. This would eliminate your concern about a parent having to have the activate() method. You could simply pass in an object with a series of id and values that could be used to generate the buttons.