I have a higher-order component that tracks the mouseover event of any composed component. It is roughly like this:
(Component) => class TrackMouseOver extends React.Component {
handleMouseOver = (e) => {
console.log('Mouse over!');
};
render() {
return <Component {...this.props} onMouseOver={this.handleMouseOver}/>;
}
};
However, this does not work very well since React components do not bind to DOM event themselves. Unless the component handle the onMouseOver prop, the HOC above won't work.
Without breaking the black-box nature of React components, is it better to always transfer props to child element? If yes, will there be significant performance impact by doing so?
If you start passing your handler function down, you will be going against the natural flow of events, as they bubble up through the DOM.
Instead, wrap your component in a handling element with a listener and make use of event bubbling.
(Component) => class TrackMouseOver extends React.Component {
handleMouseOver(e) {
e.target // is the element that triggered the event
e.currentTarget // is the element this handler is attached to
}
render() {
return (
<div onMouseOver={this.handleMouseOver}>
<Component {...this.props} />
</div>
);
}
};
When you click on one of the elements inside the new div, the event has a two part lifecycle.
Capturing
The browser works out which top level element the event happened to, then which child of that element and so on until the event finally ends up at an element with no children — the target.
This sequence won't trigger any event listeners (unless they have the useCapture flag set), it just identifies the path of the event.
Bubbling
Now the event's target element has been reached, the event bubbles back up the captured path to the root of the DOM, triggering event listeners as it goes.
This is the reason that event handlers attached to the body element always trigger*, regardless of which element the event happens to. The body is a common ancestor for all elements.
In your case, you can utilise the fact that events for any elements inside your div will eventually bubble their way up to this event handler.
You can work out which element the event came from using the event.target.
* Event handlers won't trigger if an event handler on one of their descendant elements calls event.preventDefault or event.stopPropagation.
Related
Regarding React portals, what is the difference between creating a detached node then appending it to the document vs directly having the portal rendered into the document?
In the docs (https://reactjs.org/docs/portals.html) the following is there as an example. On mount, a dynamically created DOM node (this.el) is appended to the portal div. But what's the difference between just appending it directly to the modalRoot element in the document? I don't quite get the commented section of the code.
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
// The portal element is inserted in the DOM tree after
// the Modal's children are mounted, meaning that children
// will be mounted on a detached DOM node. If a child
// component requires to be attached to the DOM tree
// immediately when mounted, for example to measure a
// DOM node, or uses 'autoFocus' in a descendant, add
// state to Modal and only render the children when Modal
// is inserted in the DOM tree.
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
}
I think it's all about descendant components.
In the example above, componentDidMount will be called, after all descendants has mounted, and only after that, all descendants will be attached to the dom.
But you can have some logic in descendants, which requires them to be in dom.
For example, you can have child component which uses ref to calculate self position in the document, this can be done only if ref is attached to real dom.
Second example, your child input can do autofocus in componentDidMount. This also will take effect, only of component is already in real dom.
In these cases, you need to prevent children from rendering, until this.el will be attached to the dom
With appendChild, the child element belongs to the same DOM and virtual DOM tree as the container. It will be affected by all cascading behaviors such as events or styles.
The Portal allows you to manage the child in a separate branch of the virtual DOM, where you have more granular control.
Simple example: if your child is a tooltip, the Portal allows you to consistently style it regardless of the container styles.
I'm trying to find a way to pass event to child component in react.
I have something like that:
class ParentComponent extends Component<IProps, IState> {
render() {
return (
<div className={"ParentComponent"} onClick={this.onPageClick}>
... some navbar ...
... some `tabs` component
...
<ChildComponent/>
<AnotherChildComponent/>
...
... some footer ...
</div>
)
}
}
the child components are actually sub pages (changed using the tabs) with lot of logic and data inside them. (so I prefer manage them in separate components rather then one giant page).
some of the inner component have Editable labels which changed into an input (or in other case to a textarea or to MD editor) when the label is clicked.
there is an inner state in the child components when the user enter
into "Edit Mode" of the label. every component can have several of
this editable-labels.
The product request is when the user is clicking anywhere in the page the labels should exit from edit mode, so I need to capture the onClick on the master div like in the example and pass somehow the event into a function into the active child component so it will update it's inner state to exit edit-mode (if any).
Now, the solution I thought is to create a state variable in the parent which will be changed by the onPageClick function and pass into the child components
so they could update the local state. and then reset it on the parent again.
something like:
onPageClick() {
this.setState({ pageClicked: true }, () => {
this.setState({ pageClicked: false }
});
}
...
<ChildComponent pageClicked={this.state.pageClicked}/>
But it will change the parent state twice per click (and thus also the child state) even if not neccesary. the ideal why if I'll find a way to pass some event delegate to the children so a function will be triggered only inside the child when the parent onClick is triggered without any state changes in the parent.
Doe's it possible? do anyone have an idea how to implement something like that?
You are making your problem way more complicated than it needs to be.
You shouldn't listen for clicks on the outside component.
Instead, you should use your text input's onBlur event.
onBlur event is fired whenever a text input loses focus.
I have a react component which has props coming from the redux store. I need to do animation for icon from this component each time when I got new props. I change state when my component gets props on componentWillUpdate(). In this way I can get animation but just the first time, then I already have this class in DOM element and new update doesn't call animation. How I see I have to delete class which provides animation from DOM, but I am not sure when to do it. I don't buttons, I have just props comes and each time when it happens I need the animation. I read that there is a way with refs, but I don't know how to use refs in such situation
Let us assume that the animation which is triggered on receipt of new props is a bounce animation, which is triggered once a bounce-class class is appended to the desired HTML element.
Instead of componentWillUpdate, I utilise the componentDidUpdate life cycle method, since I wish to call a setState when the required prop is updated. It takes the previous props and the previous state. Let us assume, that the prop which we are watching for changes is bounceProp.
componentDidUpdate(prevProps, prevState) {
if (prevProps.bounceProp !== this.props.bounceProp) {
this.setState({ shouldBounce: true });
}
}
React relies on Synthetic Events, which also includes animation-events. We use the onAnimationEnd event on the desired element, to make shouldBounce: false.
<div
className={
this.state.shouldBounce ? "bounce-class other-class" : "other-class"
}
onAnimationEnd={() => this.setState({ shouldBounce: false })}
/>;
Here the bounce-class class which is responsible for the animation, automatically removes and applies itself based on the shouldBounce variable.
I have a React component that manages an HTML audio element. AFAIU there are two ways this can be accomplished: Put the audio element as a property on the class instance with, or put it in the render() method and stick a ref on it.
Because the second option does not create the audio element directly, but via React.createElement, I can use Reacts synthetic event system - while the first option requires adding event listeners with addEventListener. My question is, if there are any advantages of the second option?
(Option 1)
class A extends React.Component {
audio = new Audio();
componentDidMount() {
this.audio.addEventListener('play', () => console.log('Started playing'));
}
play() {
this.audio.play();
}
render() {
return (
<div>
<button onClick={this.play}>Play</button>
</div>
);
}
}
(Option 2)
class A extends React.Component {
play() {
this.audio.play();
}
render() {
return (
<div>
<audio
ref={audio => this.audio = audio}
onPlay={() => console.log('Started playing')}
/>
<button onClick={this.play}>Play</button>
</div>
);
}
}
Synthetic event is just a wrapper for normal events. They are there to provide a common interface between browser inconsistencies and large amounts of events ultimately perform better because of event pooling.
You are access the orginial event through e.nativeEvent.
Synthetic events are better in the sense that they are easier to use (much less code, supported without hassle between all browsers) and they are more reliable since React adds them after the element in rendered and is available in the DOM. When componentDidMount is called you can't be sure if the element is actually present in the browser DOM. componentDidMount is called when React tells the browser to append the element to DOM and has calculated the necessary stuff in it's own virtual DOM. But the browser may not render the element if the render thread is busy in any way.
Is there a Reacty way to create a wrapper component of another element that has bound DOM events, without producing another element (as in <div>)?
E.g.
class TripleClickWrapper extends Component {
render() {
return <div onClick={::this._onClick}>{this.props.children}</div>
}
_onClick() { /* counts clicks and handles timeouts etc */ }
}
// somewhere else:
<TripleClickWrapper onTripleClick={::this._doSomething}>
<SomeComponent />
</TripleClickWrapper>
I don't want the extra <div> TripleClickWrapper creates, but I want to bind onClick to the wrapper, without passing it down to <SomeComponent>. Any nice way without getting to DOMy (findDOMNode+addEventLisetener+remove on unmout)?
If I didn't need to bind DOM events, I could just return React.Children.only(this.props.children).
You can return an augmented/cloned version of the child component in your render function: https://facebook.github.io/react/docs/top-level-api.html#react.cloneelement
You can add additional props during the cloning process.
render() {
const newProps = /* any props/event handlers you want to add */;
return React.cloneElement(React.Children.only(this.props.children), newProps);
}
Based on the source code (specifically, this line: https://github.com/facebook/react/blob/v15.0.0-rc.2/src/renderers/dom/shared/ReactDOMComponent.js#L621 ) I see that a possible solution might be to inject an EventPlugin. This is because enqueuePutListener is called, which in turn calls ReactBrowserEventEmitter.listenTo --> ReactEventListener.trapBubbledEvent --> EventListener.listen which does the actual addEventListener. The if statement there looks for a plugin name. I'm not sure how that mechanism works, and didn't find it in the docs, but with some investigation sounds like a possible solution. For example, there's a plugin that handled tap events when 300ms delay was still a thing.
I ended up with this approach:
I didn't want an extra <div>
I didn't want my wrapper to override the child's events (so if my wrapped div already had onClick it should still run
I didn't want to use addEventListener manually on the findDOMNode(this). Reasons: 1) inner onClicks can't run e.stopPropagation() and stop my added event, as React events run on the document, without useCapture, and 2) lose React event system goodies.
Currently I'm taking the only child in the wrapper and use cloneElement, as #Calvin Belden recommended, and pass in my events combined with existing events.
render() {
const onlyChild = React.Children.only(children)
const events = {
onClick: this.doSomething,
onMouseEnter: this.doSomethingElse,
// ...
}
for (let [eventName, fn] of Object.entries(events)) {
if (onlyChild.props[eventName]) {
let oldFn = onlyChild.props[eventName]
events[eventName] = e => {
oldFn(e)
if (!e.isPropagationStopped()) fn(e)
}
}
}
return React.cloneElement(onlyChild, events)
}
It still forces me to pass in events when I use a component instead of a div: <TripleClickWrapper><SomeComponent></TripleClickWrapper>. SomeComponent would have to pass down onClick to its div.
Not optimal, but as clean as it can be.