Get notified when React tree is updated - javascript

ReactDOM.render accepts an optional callback, which is executed when component is rendered:
ReactDOM.render(element, container[, callback])
Is there a similar callback in React/ReactDOM that is executed when a component in the tree (of any depth) is updated from within, i.e. using a setState?
Simply providing componentDidUpdate on the root component won't do, as the method is not triggered on children update: https://codesandbox.io/s/react-example-yjq0r
It is possible to subscribe to DOM tree updates using MutationObserver, but I wonder whether React provides this functionality out of the box.

This can be done using React.Profiler:
<React.Profiler id="app" onRender={callback}>
<Root />
</React.Profiler>
https://codesandbox.io/s/react-example-lmvzk

Related

get ref once all descendants are finished rendering

I'm working on a react-leaflet custom control, and I have a component whose ref I need. But this component has dynamically rendered children. Something like this:
<LayersControl ref={controlRef => this.controlRef = controlRef}>
{this.props.children}
</LayersControl>
The children have their own children as well. As these children are rendered, they affect the underlying leaflet logic.
(For those not familiar, or curious, LayersControl will create an L.control.layers. The children of the LayersControl will always be a LayersControl.BaseLayer or LayersConrol.Overlay. Those will then have children, which could be any number of not-yet-known react-leaflet layers or components. As the .BaseLayer or .Overlay components mount, they modify the underlying L.control.layers object.)
I am trying to get a reference to the leaflet instance of the component after all descendants have rendered. However, calling the ref function as I've called above, that's not what happens. While my ref is indeed defined in the componentDidMount, it is incomplete. For example, some of the crucial properties that I need from within cdm are not yet available (i.e. the ._container property).
I know that his is how refs are intended to work - they give you a ref as soon as the root element has rendered and mounted, even if the children and descendants have not yet mounted. Short of doing some hack like doing a setTimeout and getting the ref after 10ms, how can I get a ref of this element once all descendants are done rendering?
Working demo of the issue

React ref attribute using callback attaches a DOM node to my class

I am currently using strings for ref= in React 15.3.2, and such is deprecated.
So if I use a callback per the docs:
ref={(input) => this.textInput = input}
Then this will attach a DOM Element to my class when the component mounts.
Yay! A DOM element. So now I do not have to do:
ReactDOM.findDOMNode(this.refs.input).value=''; // uncontrolled component
I thought the whole idea of react is to not touch the DOM...
Lets assume I have a TextInput component that is complex, and has an InputError component along with <input type="text" -- this TextInput component does a bit of validation based on props, and it has a state that helps things like when to show error.
Back up in the form, I want to clear the form, so TextInput has a .clear() which resets its state. (Am I doing it wrong already?)
The thing is, I cannot access any child components React Objects unless I use strings as ref=, so I cannot call clear().
What gives?
Am I supposed to route "all communication" through props? What good is this.refs once all refs are callbacks? Can I get my react objects through .children or something? What is the convention on this issue?
Edit:
Obviously I am learning React, I guess the basic question is, is it bad (anti-react) to EVER call methods on a child component?
The components in question can be found here:
RegisterForm.jsx
TextInput.jsx
InputError.jsx
The requirement I find difficult/strange making work via props is TextInput: "onblur then if error then show error, mark as errored until changing passes validate"

React: Bubbling up click events on nested components

I'm creating a react file tree, and I have the tree setup as a React component. The tree can take a contents prop that is an array of either strings, or other <Tree /> components (this enables the nested file structure UI). These tree components can be nested indefinitely.
I need to register a click event on the children of the nested tree components, but I'm having trouble getting it to work beyond the first level of nesting. A simplified example of what I'm dealing with:
//In App - the top level component
const App = React.createClass({
_handleChildClick () {
console.log("this is where all child clicks should be handled");
},
render () {
return (
<Tree
handleChildClick={this._handleChildClick}
contents={[
<Tree />
]}
/>
);
}
});
//And in the tree component
<div onClick={this.props.handleChildClick}></div>
If you want to see more detail - here's the github repo.
I tried researching this question and saw people using {...this.props} but I'm not sure if that applies to my scenario - if it does, I couldn't get it to work.
Thanks for any help on this.
The reason why the click handling does not work beyond the first level is because your second level Tree component (the one inside the contents array) does not get the appropriate prop handleChildClick passed in. (BTW I think the convention is to call the prop onChildClick while the handler function is called handleChildClick - but I digress.)
Do I understand correctly that you actually want to inform each layer from the clicked component up to the top? For this to happen, you need to extend the props of the tree component that is inside the contents array - it needs to receive the click handler of its parent component. Of course, you cannot write this down statically, so it needs to be done dynamically:
Your Tree component, before actually rendering its children, should extend each of them with the component's click handler, which can be done using the function React.cloneElement (see API documentation and a more detailed discussion). Directly applying this to your component makes things a bit messy, because you are passing the component's children in a prop, so you need to figure out which prop to modify. A bit of a different layout would help you quite a lot here:
<Tree handleChildClick={this._handleChildClick}>
<Tree />
</Tree>
looks nicer IMHO and makes the structure much clearer. You can access the inner components via this.props.children, and cloneElement will be much simpler to use.
So, in your Tree component, you could have a render method like this:
render () {
const newChildren = this.props.children.map(child =>
React.cloneElement(child, {onChildClick: this._handleChildClick}));
return (
<div>{newChildren}</div>
);
}
Please note that this code will not work if you have a mixture of strings and Tree components, therefore my third and last suggestion would be to wrap those strings into a very thin component to allow for easier handling. Alternatively, you can of course do a type comparison inside the map.

How to prevent recreation of a list when a child property changes?

I have a component called <Events events={Array} activeEventId={String} />. Events component renders a list of <Event event={Object} isActive={Bool} /> components, e.g.
render () {
let activeEventId,
events;
events = this.props.events;
activeEventId = this.props.activeEventId;
return <div>
{events.map(function(event) {
return <div key={event.id} >
<Event event={event} isActive={event.id === activeEventId} />
</div>;
})}
</div>;
}
Changing activeEventId recreates the entire list of Events every time.
How to change the implementation that only the Event whose isActive prop has changed would get re-rendered?
Edit: Both Events and Event components are pure in that they only rely on their props, and don't use state at all.
You can pass the event object and the activeEventId value to the Event component as props. Then you can implement shouldComponentUpdate to check whether it should re-render or not.
shouldComponentUpdate: function(nextProps, nextState){
//check if activeEventId changed
return nextProps.activeEventId != this.props.activeEventId
}
You can achieve it using the one of the React lifecycle method and it is shouldComponentUpdate
You have to check in shouldComponentUpdate that you have to call the render method of your component or not if you return false then it won't call your render method for more information use shouldComponentUpdate
if you want to achieve it based on conditional then you have mark one flag which can hold the value of true and false and you can apply in your render method like
{isRender==true? <Event event={event} isActive={event.id === activeEventId} />:null}
There are two approaches you can take here:
1) Implement shouldComponentUpdate on the Event component. Here's a fiddle with your example using a rudimentary shouldComponentUpdate method. In the log, you can see that only 2 Events get re-rendered (the one that became active and the one that is no longer active). If the Event components are getting destroyed in your code, you're probably not generating your keys correctly, make sure they stay consistent between renders.
2) Assuming you're using some variant of Flux, you could have your Event components listen on a store and keep the isActive flag within their state, updating it when the store triggers the corresponding change. This is a bit more efficient, since it does not repeatedly call EventList.render or even Event.shouldComponentUpdate. Only the Events that actually change respond to the change by updating their state and re-rendering. This introduces more complexity however, since it forces you to put state in child components, which is always something you'd like to avoid. I'd definitely try to go with 1) unless you really need to optimize every bit.

Is my React component being recreated instead of updating?

I am trying to combine Angular and React.js. I have an work example project here I have seen a couple of ways to bring the Angular and React.js together. One of the methods I have seen is to create a directive and create the React component in the link function. For example in the first part of the project to generate the React version(in red) I am using
.directive('reactElementRepeater', function() {
return {
link: function(scope, element) {
var update_react = function(oldVal, newVal){ //Called every time one of the two values change
React.renderComponent(Demo_Element({
numberOfElements: scope.myModel.numberOfElem,
numberInElements: scope.myModel.numberInElem
}), element[0]);
}
scope.$watch('myModel.numberOfElem.length', update_react);
scope.$watch('myModel.numberInElem', update_react);
}
}
});
What I want and what should happen in a React enabled application is for something in the model to be updated, then that update is sent through React and it will alter the DOM as little as possible to reflect that change. It looks like that instead of updating a bit of the DOM this will Create a new React component each time with renderComponent.
React.renderComponent() instantiates the root component, starts the
framework, and injects the markup into a raw DOM element, provided as
the second argument.
Is it actually recreating the elements each time? If that is the case is there a way to alter this so that doesn't happen?
Just to be clear I know about ngReact, I just want to know other ways to speed up Angular with React.
Yes this is fine, it's not mounting the component multiple times.
When you call React.renderComponent() the second argument is the element which react should render the component to. So react notices if you are rendering the same component to a dom element that already contains a mounted instance of the component, and does not re-mount the entire component, it just updates the properties of it instead.
You can see this in action if you make a component with componentDidMount function defined. You'll notice that componentDidMount will only execute the first time renderComponent gets called. And afterwards, subsequent calls to renderComponent on the same target dom element will not call it because the component is already mounted. Likewise getDefaultState and getDefaultProps also only get called on the first renderComponent call.
If you're asking will the render function of the component be called every time the answer is yes. But this is how react works, you want the render function to get called because props might have changed. You can block it from being called by using shouldComponentUpdate (http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) and returning false. However react developers recommend you don't use this to block render calls unless you have specific performance problems - most of the time it should be fine to just let the render call execute as it wont cause any slow dom updates unless things have actually changed.

Categories