Is my React component being recreated instead of updating? - javascript

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.

Related

Prevent unnecessary re-renders of components when using useContext with React

I've seen a few questions related to this topic, but none that tackle the issue head-on in a pure way. useContext is a great tool for minimizing prop-drilling and centralizing key data needed across your app; however, it comes at a cost that I'm trying to minimize.
The closest issue to the one I'm describing here was asked two years ago here. The question didn't gain a lot of traction and the only answer basically says call the context in a parent container and pass values down through props (defeats the purpose of context) or use Redux. Maybe that's the only way, but I wanted to bring the question back to the collective to see if there is a better answer that's not dependent on external libraries.
I've set up a code sandbox here to illustrate the issue. Re-renders are console logged out to make seeing them easier.
In short, when a state changes in context, every app accessing data from that context re-renders, even if it's not utilizing the state data that changed (because it's all ultimately passed through the value object). React.Memo does not work on values accessed from context the same way it does for properties.
For example, in the code sandbox linked above, in App.js if you comment out <AppContainerWithContext /> and load <AppContainer />, there are two states managed in the container, one called titleText and the other called paragraphText. titleText is passed as a prop to component called TitleText and paragraphText is passed to a component called ParagraphText. Both components are wrapped in React.memo(). There are two buttons called in the AppContainer and each has a function that changes the text back and forth based on the value of separate boolean states.
Here is the function that toggles the titleText, the one for paragraph text is the same logic:
const changeTitleHandler = useCallback(() => {
const title = listTitleToggle ? "Title B" : "Title A";
setListTitleToggle((pV) => !pV)
setTitleText(title);
}, [listTitleToggle]);
Since the titleText component and paragraphText components are wrapped with React.useMemo, they only re-render when the corresponding value passed to them changes. Perfect.
Now in App.js if you comment out the <AppContainer /> component and enable the <AppContainerWithContext /> component, the rendered output and result of button clicks is identical; however, the states that change and are rendered on the screen are now managed by AppContext (called contextTitleText and contextParagraphText and passed to the TitleText component and ParagraphText component via useContext.
Now, if you click on the button to toggle the title, the ParagraphText component re-renders too, even though it doesn't use the contextTitleText state. My understanding of why this happens is because the value object changes when the contextTitleText is updated, causing any component accessing that value object through useContext to re-render.
My question is this:
Is there a way to utilize useContext without causing re-renders on all components accessing the context. In the example above, can we utilize useContext to manage the contextTitleText and the contextParagraphText but only re-render the components where the state from context being accessed changes?

React/Flux implementation technique is unclear for when a parent component needs to pull strings on child component

I have a situation which isn't too contrived, and I'm having trouble implementing it using the React best practices. In particular it produces this error:
Uncaught Error: Invariant Violation: setProps(...): You called setProps on a component with a parent. This is an anti-pattern since props will get reactively updated when rendered. Instead, change the owner's render method to pass the correct value as props to the component where it is created.
The situation is like this. The parent contains a child component. The parent has event handlers for UI and for the behavior to work, something inside the child component needs to render its HTML with a CSS change to the height style. Therein lies the wrinkle, usually the information flows upward or stays put, but here I need to change something in the child.
Parent component (Widget) renders this:
<div class="Widget">
<div class="WidgetGrabBar" onMouseDown={this.handleMouseDown}>
<WidgetDetails heightProp={this.props.detailsHeight} />
</div>
And elsewhere in Widget I've got
componentDidMount: function() {
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
},
componentDidUnmount: function() {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
},
<...>
handleMouseDown: function(e) {
e.preventDefault();
this.props.actuallyDragging = true;
},
handleMouseUp: function(e) {
this.props.actuallyDragging = false;
},
handleMouseMove: function(e) {
e.preventDefault();
if (this.props.actuallyDragging) {
// update the prop! I need to send an urgent package of information to my child!! jQuery or findDOMElement() followed by DOM traversal is forbidden!!!
this.setProps({
detailsHeight: this.props.detailsHeight + e.deltaY
});
}
},
And I had WidgetDetails' render() render something like:
<div class="WidgetDetails" style={height: this.props.heightProp}>
{detail_items_move_along_nothing_to_see_here}
</div>
I figured that rolling out the jQuery to grab .WidgetDetails to fiddle with its style attr is the wrong thing, the non-React way to go about it. The real anti-pattern.
But now I'm being told that I can't change my props. Or I have to throw out everything including the bathwater in order to have new props. I'm not doing that; my props contain the contents of the detail items. Maybe it is expensive to make another entirely new copy of this.
I'm trying to let React participate in this rendering work to put the new height in. How am I supposed to even do this? Is this error basically enforcing that Props are supposed to be immutable now? The error is telling me that I have to involve this height even farther up on the component chain. I can conceivably do so with a callback from up above, but this feels very wrong. I need to pass information downward, not upward.
Maybe I'm supposed to use state. But changing state forces Widget, the parent component to render. That is not what I desire. Only one singular place in the DOM needs to re-render, that is the child component's div's style attr.
There are two approaches. Either
call handlers on the parent. Then Pass the new props to the child via props. If I recall correctly, that's the approach the react hello world tutorial takes.
Mutate state in the view via setState.
In your case, it seems that approach 2 really makes sense. You are basically trying to track view data.
Never, by the way, update state directly. Use setState. The whole point of reacts virtual dom is that it's optimized for spurious updates, so you will be fine. There is also the life cycle method componentShouldUpdate in case you want finer control.
For completeness I should add that there's a third way of using a global store. That's what react flux adds. But again, in your case that's probably over kill.

setState cause Invariant Violation addComponentAsRefTo

I'm using react-rails with Fluxxor and React. My component is fully functional as long as I stay on the same page.
However, if I change the page by clicking on an other link and come back to my component, when I try to use setState on it, It throw the error :
Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs. This usually means that you're trying to add a ref to a component that doesn't have an owner (that is, was not created inside of another component's `render` method). Try rendering this component inside of a new top-level component which will hold the ref.
My actual code can be found here. The problem seems to be the setState method here. Maybe the refs attribut of my Chosen component can't be re-render ? May it be caused by Turbolink ?
I think it's a problem in the implementation of the Chosen component.
When it's given new props, React re-renders it and puts new nodes in the page. However, $.fn.chosen has already been instantiated, and it's attached to DOM nodes that aren't in the page anymore. I suspect that, somewhere along the way, references to old nodes and components are being preserved, even after they're unmounted.
I had the same problem using select2 with React. I found Ryan Florence's guide to jQuery + React to be very helpful:
We need a way to stop rendering with React, do the jQuery dialog work, and then start rendering with React again. Some people call these "Portals". You open a portal for React to skip over a bit of old-school DOM stuff, and then keep going on the other side.
The big trick is rendering nothing and then calling React.renderComponent inside a component.
var Dialog = React.createClass({
render: function() {
// don't render anything, this is where we open the portal
return <div/>;
},
componentDidMount: function() {
var node = this.getDOMNode();
// do the old-school stuff
var dialog = $(node).dialog().data('ui-dialog');
// start a new React render tree with our node and the children
// passed in from above, this is the other side of the portal.
React.renderComponent(<div>{this.props.children}</div>, node):
}
});
source https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md

ReactJS - Does render get called any time "setState" is called?

Does React re-render all components and sub components every time setState() is called?
If so, why? I thought the idea was that React only rendered as little as needed - when state changed.
In the following simple example, both classes render again when the text is clicked, despite the fact that the state doesn't change on subsequent clicks, as the onClick handler always sets the state to the same value:
this.setState({'test':'me'});
I would've expected that renders would only happen if state data had changed.
Here's the code of the example, as a JS Fiddle, and embedded snippet:
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
Does React re-render all components and sub-components every time setState is called?
By default - yes.
There is a method boolean shouldComponentUpdate(object nextProps, object nextState), each component has this method and it's responsible to determine "should component update (run render function)?" every time you change state or pass new props from parent component.
You can write your own implementation of shouldComponentUpdate method for your component, but default implementation always returns true - meaning always re-run render function.
Quote from official docs http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate
By default, shouldComponentUpdate always returns true to prevent
subtle bugs when the state is mutated in place, but if you are careful to
always treat the state as immutable and to read-only from props and state
in render() then you can override shouldComponentUpdate with an
implementation that compares the old props and state to their
replacements.
Next part of your question:
If so, why? I thought the idea was that React only rendered as little as needed - when the state changed.
There are two steps of what we may call "render":
Virtual DOM renders: when render method is called it returns a new virtual dom structure of the component. As I mentioned before, this render method is called always when you call setState(), because shouldComponentUpdate always returns true by default. So, by default, there is no optimization here in React.
Native DOM renders: React changes real DOM nodes in your browser only if they were changed in the Virtual DOM and as little as needed - this is that great React's feature which optimizes real DOM mutation and makes React fast.
No, React doesn't render everything when the state changes.
Whenever a component is dirty (its state changed), that component and its children are re-rendered. This, to some extent, is to re-render as little as possible. The only time when render isn't called is when some branch is moved to another root, where theoretically we don't need to re-render anything. In your example, TimeInChild is a child component of Main, so it also gets re-rendered when the state of Main changes.
React doesn't compare state data. When setState is called, it marks the component as dirty (which means it needs to be re-rendered). The important thing to note is that although render method of the component is called, the real DOM is only updated if the output is different from the current DOM tree (a.k.a diffing between the Virtual DOM tree and document's DOM tree). In your example, even though the state data hasn't changed, the time of last change did, making Virtual DOM different from the document's DOM, hence why the HTML is updated.
Yes. It calls the render() method every time we call setState only except when shouldComponentUpdate returns false.
Even though it's stated in many of the other answers here, the component should either:
implement shouldComponentUpdate to render only when state or properties change
switch to extending a PureComponent, which already implements a shouldComponentUpdate method internally for shallow comparisons.
Here's an example that uses shouldComponentUpdate, which works only for this simple use case and demonstration purposes. When this is used, the component no longer re-renders itself on each click, and is rendered when first displayed, and after it's been clicked once.
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state == null)
return true;
if (this.state.test == nextState.test)
return false;
return true;
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
It seems that the accepted answers are no longer the case when using React hooks (with primitive values, see comments on this answer for details). You can see in this code sandbox that the class component is rerendered when the state is set to the same value, while in the function component, setting the state to the same value doesn't cause a rerender.
https://codesandbox.io/s/still-wave-wouk2?file=/src/App.js
React 18 and beyond
Starting from React 18 all state updates are automatically batched. In this way, React groups multiple state updates into a single re-render for better performance.
So when you update your state, React always try to batch these updates in a group update, causing fewer render than setState calls. The behaviour is the same when using hooks.
You can read the very long explanation in the Automatic batching for React 18 announcement.
React 17 and below
In React 17 and below, only updates inside React event handlers are batched. Updates triggered from promises, setTimeout, native event handlers, or other events are not batched in React by default.
Another reason for "lost update" can be the next:
If the static getDerivedStateFromProps is defined then it is rerun in every update process according to official documentation https://reactjs.org/docs/react-component.html#updating.
so if that state value comes from props at the beginning it is overwrite in every update.
If it is the problem then U can avoid setting the state during update, you should check the state parameter value like this
static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}
Another solution is add a initialized property to state, and set it up in the first time (if the state is initialized to non null value.)
Not All Components.
the state in component looks like the source of the waterfall of state of the whole APP.
So the change happens from where the setState called. The tree of renders then get called from there. If you've used pure component, the render will be skipped.
Regardless of the well explained answers here, there may be other reasons why you don't see the change you expect post changing the props or state:
Watch out for any event.preventDefault(); in the code where you want to re-render by a state \ props change, as it will cancel any cancelable event following this statement.
You could use setState() only after comparing the current state value and the new one and they are different.

Idiomatic way to listen to model changes

I'm playing with React for the first time and I think I really like it. I've implemented (large parts of) the board game Go with it and so far, but I've run into something strange that I don't know how to approach in the idiomatic React way. Basically, I've got a model--the board game--implemented as its own class Board. It exposes only it's constructor, and methods play(i,j) and pass. It handles all of the game logic and updates its own internal state appropriately. It has no reference to anything related to a view/component. I've got a React Component called BoardView which maintains a reference to an instance of a Board. I've also got a Component called AlertView that displays messages about the game state (illegal moves and such) when appropriate.
Everything works well now, and I like the separation of concerns between the Board class and its views. However, the way I have my Board class communicate its changes to the views is unusual, and I feel that it is inconsistent with other React code. Basically, I abuse jQuery's event system to allow me to trigger arbitrary events like ["update", "atari", "suicide"]. In this scheme, the Component has an onClick listener that calls Board.play, which triggers 0 to many events on the Board instance. The Component listens for an "update" event, and calls this.setState, which will force it to re-render(), putting the view into a state that correctly depicts the game. The AlertView listens for the "atari" and "suicide" events on the same board instance and similarly calls this.setState, which triggers another render().
Should I cut out the jQuery events? If so, what's the best way of doing this?
All code is available here and you can play with the app here.
Edit:
For posterity's sake, this question was asked at commit 3f600c.
I'm not sure if this is idiomatic React, but from the React tutorial, the onSubmit handler is passed from the parent to the children as a props.
In your case that would mean to pass the onPlay handler from BoardView to BoardIntersection like this:
var BoardView = React.createClass({
getInitialState: function() {
return {"board": this.props.board}
},
playHandler: function(i, j) {
this.props.board.play(i, j)
},
render: function() {
...
intersections.push(BoardIntersection({
color: this.state.board.board[i][j],
row: i,
col: j,
onPlay: this.playHandler
}));
...
}
})
and BoardIntersection will call onPlay as needed:
var BoardIntersection = React.createClass({
handleClick: function() {
this.props.onPlay(this.props.row, this.props.col);
},
})
tungd's comments pointed me in the right direction, but I decided to answer my own question for a more complete answer.
I ended up removing all of the custom events being fired on the model. I found the following snippet from the React docs to be especially helpful:
A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via props. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
Instead of firing events like "atari" and "suicide" on the model, I just set boolean properties on the model in_atari and attempted_suicide. Now, only one "parent" Component in my application has state. It renders all sub-components via declarative props. The AlertView is one such sub-component whose render method now checks the new boolean flags to render the appropriate text. The main parent Component passes a handler to its sub-components that updates the component state (and subsequently forces a re-render).
In the relevant commit, I've named the parent component ContainerView.

Categories