React: how to compare current props.children with new one - javascript

Hi,
i am building component which acts only as wrapper for some other generated content and uses third party library. This library works with props.children of component. So far so good, but this thrird party library is little laggy when applied, or refreshed on element. And because only reason to refresh this library is when props.children changed I am trying to figure how to compare this.props.children and nextProps.children in shouldComponentUpdate. I was thinking that PureRenderMixin should do the work, but for me it does not works. Component is rerendered even if I change only state.listName as it is in example below.
<div>
List name '{this.state.listName}'
<br />
<MyComponent>
<ul>
{listOfLi}
</ul>
</MyComponent>
</div>
Is there any way, how to manage comparing of props.children or any other option how to do something like that?
Thanks for any help!

As Matt S pointed out, the accepted answer is kind of a fragile workaround and would depend on a non-standard use of key. Aside from the list examples he listed, even using something like key={id} would fall down if your ids remained the same but certain fields were modified in the resources they represent.
This issue contains a good discussion on the topic and ends with a more stable workaround. Essentially, you can simplify the children prop in a way that allows you to run a deep comparison. You can use the React.Children utilities to write the simplification methods:
// Flattens all child elements into a single list
const flatten = (children, flat = []) => {
flat = [ ...flat, ...React.Children.toArray(children) ]
if (children.props && children.props.children) {
return flatten(children.props.children, flat)
}
return flat
}
// Strips all circular references and internal fields
const simplify = children => {
const flat = flatten(children)
return flat.map(
({
key,
ref,
type,
props: {
children,
...props
}
}) => ({
key, ref, type, props
})
)
}
Then you can use shouldComponentUpdate or React.memo to prevent re-renders:
const MyComponent = ({ children }) => (
<div>{ children }</div>
)
export default React.memo(MyComponent, (prev, next) => (
JSON.stringify(simplify(prev.children)) ===
JSON.stringify(simplify(next.children))
))
These utilities + JSON.stringify are just one approach, the one mentioned in the comment is similar and you could also leverage utilities like lodash.isequal for the deep comparison. Unfortunately I don't know of any one or two liners for this comparison but please comment if you know a simpler stable way to do this!

You can make use of child key prop that React suggests that arrays of children should be given to uniquely identify them. Because each child has a key, you can reliably tell whether children has changed across prop changes (this is the entire point of keys!). If the keys don't match between new and old then they have changed.
React.render(<App><Child key='1'/><Child key='2'/></App>, document.body)
and do the check in the App component you want to check before each update if the children changed
shouldComponentUpdate(nextProps){
var oldKeys = this.props.children.map( child => child.key);
var newKeys = nextProps.children.map( child => child.key);
//compare new and old keys to make sure they are the same
}
Note that this doesn't tell you if the content of each child has changed, you have to compare each by some criteria (such as deeply comparing props) if you want to know if nothing in the whole tree below this point has changed
as an even further optimization we know that children will never change as result of a state change so we can actually do our comparing in componentWillReceiveProps() and just set some state property like childrenHaveChanged

Something about the design and behavior you describe is off. You should rarely, if ever, have to concern yourself with performing manual diffs of children. That should be left to React. If the library you are using is choking every time the component updates, regardless of whether it's because of children or some other state/prop change, it is not written reactively and you should look for a different one that is written reactively. Or you could contribute to fixing that behavior in the open source project, either by opening an issue or submitting a pull request.
It's not easy to do what you're trying to do because it shouldn't be necessary. React is very, very good at handling reconciliation and will only render when something has actually changed that will change the state of the DOM relevant to it.

Related

Skip rendering children in React

Is there a way in React to skip over rendering children? I have existing DOM nodes on a page that I'd just like to wrap in a React component.
My use case is rendering the frame but not the static contents, which are pre-existing DOM nodes.
Something like:
const Frame = (props) => (
<div class="frame">
<SkipRender>{props.children}</SkipRender>
</div>
)
I'm guessing, when you say 'static children', you mean you don't want to rerender them? If so, then I have a solution for you.
React has a feature called memoization, which remembers a value and only updates it when the values it depends on (its 'dependencies') get updated. We can implement it through the useMemo() hook. A SkipRender component will look something like this:
function SkipRender(props) {
const children = useMemo(() => {
return props.children;
}, []);
return children;
}
What we're doing here is taking in the passed children elements, memoizing it (remembering its initial value), and keeping it static. The empty dependency array [] on the 4th line means children variable doesn't have any values it depends on, so it will not reevaluate its value. Since we're storing React elements to it, this will make it so those elements won't rerender after the initial render, even if the passed values change. Let me know if this helps, or if you need more clarification!

Set component prop to const array/object or re-create each render?

Curious in the performance or other benefits of using const variables where possible when setting a component's props verses setting them inline. Please include supporting ReactJS documentation if applicable.
Potential benefits of using const variables in my head:
Reduced overhead creating objects on every render.
Fewer cluttered code?
Fewer lifecycle/render loops? (This is my main concern.)
Example 1: Inline / Traditional
export const SomeContainer(...) => (
<Something validators={[required(), minLength(3), maxLength(10)]} />
);
Example 2: Via const Variables
export const SomeContainer(...) => (
<Something validators={Validators} />
);
const Validators = [required(), minLength(3), maxLength(10)];
In Example 1, the validators prop is a new array on each render while in Example 2, a constant value is passed to the validators prop.
Same question with:
inline vs. const objects ...
value={{a, b, c}}
value={ConstWithABC}
inline vs. const callbacks ...
onChange={value => setState({ x: value })}
onChange={handleChange} (handleChange is an instance function/const lambda)
React can track the state changes easily. So re-rendering the state owner component(let's call it XComponent) directly is the first point. Then, as you already know, a re-render flow will start upon all XComponent's children tree recursively.
While doing this, React will check prop changes to decide which children should re-render like:
if the prop is primitive, there will be a simple value comparison
if the prop is an array, function, or object(that's the place you were mentioning in your question), there will be a reference comparison directly. That's why there are two hooks for this purpose: useMemo and useCallback. These hooks help you to store the reference or value result from your in-component definitions/calculations. So, your second example most probably will not trigger a re-render because of the reference equality. And then React can stop trimming re-render tree after <Something /> and its possible children.
I experienced this while changing a prop array item in the past. I saw that React didn't even realize that I changed the prop array's length from 3 to 2 because of the same array reference. But I was seeing that there are still 3 items on the screen.
if the child is a result of some iteration or mapping like:
const Comp1 = (props) => {
return <div>
{props.data.map((item) => <Comp2 key={item.id} propX={item.x}/>)}
</div>;
};
then, there will be a key comparison for understanding which child should be added or removed from the render.
https://reactjs.org/docs/lists-and-keys.html#keys
And React docs suggest that you should use a primitive type for keys to make the comparison easier.
Overall, there is a doc for a better explanation https://reactjs.org/docs/reconciliation.html
basically your examples are all just values, I don't see much of a performance difference.
There will be a difference if you do the following, however.
<Something value={[1000 numbers array].reduce((a,b) => a+b))} />
vs
const someNumber = useMemo(() => [1000 numbers array].reduce((a,b) => a+b)), [1000 numbers array])
return <Something value={someNumber} />
In above example it's calculation x 1000, vs calculation x 1, if you were to render the page 1000 times.
and if you don't want const Validators = [...] to be re-rendered every time, you can basically put it outside the component as global const variable and it gets rendered only once.
E.g. if you want to declare a styled component, it's always advised to declare the styled component OUTSIDE the component, so that you only render it once.
And with regards to your following example.
inline vs. const callbacks ...
onChange={value => setState({ x: value })}
onChange={handleChange} (handleChange is an instance function/const lambda)
You are just referring to a single setState. What if it's function with 100lines of codes? You gonna throw that in the return clause?
Many times, other than performance, we also consider readability. Even if writing the function within the return clause provides better performance, I will never do that.

The best way to remove a key from react component's state

I am using a react class component, which holds a state, with (lets say..) a lot of key-value pairs stored into it.
Upon user action (button press/toggle), I need to REMOVE / ADD a new key-value pair to the component's state. Adding one is relatively easy, but pulling out a key-value pair from the state can be done in several different ways, so I was wondering which one is best, most readable, most performant and most preferred by the ReactJS audience..
1) Option 1:
onRemove = key => {
const newState = this.state;
delete newState[key] // Interacts directly with the state, which might not be a good practice, or expended behaviour?
this.setState(newState);
}
2) Option 2:
onRemove = key => {
const {[key]: removedKey, ...newState} = this.state; // Defines two new variables, one of which won't be used - "removedKey";
this.setState(newState);
}
There might be more ways to do it, and I am wondering which might be the best one, that can be used in any circumstance, no matter how 'big' the state gets...
Please share your thoughts based on your work experience with React & State management!
Thanks!
When I do something like this, I generally do a modified version of your "Option 1." As you currently have it, Option 1 mutates the state object, which you shouldn't do. Instead, create a shallow copy of state and then delete the key.
onRemove = key => {
const newState = {...this.state};
delete newState[key];
this.setState(newState);
}
The reason I like this way over your Option 2 is subjective--It's very readable to me. Make a copy, delete a key. Simple and to the point.
Option 1 isn't an option. You can't directly modify state in React.
Option 2 is fairly standard practice.
A third (to my mind lesser) option is to remove the property after cloning the object, but deleting properties from objects isn't what JavaScript engines optimize for, so I don't see any advantage to it over Option 2.

Which places and for what elements do we have to provide a unique key?

I always thought the only place that a unique key is needed is inside lists and arrays like map, but today i was writing a loading screen for my component so a loading is shown to the user and after the load ends i do a setState and let the render know that its time to render the real view.
But i saw that the new component is not being rendered and the loading remains on the screen! after lots of testing i saw everything works as it should and finally i thought lets give it a key maybe something happens!! and it did !, the problem was solved, and i was confused as why on earth a key was needed there
So here is a sudo code of what i did and what happend :
render () {
//with no key, this doesn't render properly
const finalView = this.state.isLoading ?
<UIManager key={1} json= {myLoadingComponentJSON} />
:
<UIManager key={2} json={myFormJson} />;
return () {
finalView
}
}
I can defiantly see that the problem here is probably that i am using a component called UIManager and i use a JSON to know what kinda element this component should render, and probably both UIManagers had the same key? well i am not sure, but still i didn't think a key was needed here.
Which places and for what elements do we have to provide a unique key?
Only provide a key for siblings where you expect the items to be re-ordered (think of list items).
If a child is unique (i.e doesn't have siblings), no need for a key
key should be unique across siblings with the same ancestor.
So for the case of UIManager, you can supply a name as key.
i.e
const finalView = this.state.isLoading ?
<UIManager key={'LoadingComp'} json= {myLoadingComponentJSON} />
:
<UIManager key={'Form'} json={myFormJson} />;
Why?
The real cause is how React does Reconciliation. Without the key, React only sees json attribute and check if it changed or not (might have performance problems with deeply nested JSON data - slow / dropped frames). If it sees any change in json, it will destroy previous instance of UIManager.
See Tradeoffs due to heuristics.
Summary:
With supplying a key, it makes it easier for React to check the difference.
Reconciliation
React provides a declarative API so that you don’t have to worry about exactly what changes on every update. This makes writing applications a lot easier, but it might not be obvious how this is implemented within React. This article explains the choices we made in React’s “diffing” algorithm so that component updates are predictable while being fast enough for high-performance apps.
Because React relies on heuristics if the assumptions behind them are not met, performance will suffer.
The algorithm will not try to match the subtrees of different
component types. If you see yourself alternating between two-component
types with very similar output, you may want to make it the same type.
In practice, we haven’t found this to be an issue.
Keys should be stable, predictable, and unique. Unstable keys (like
those produced by Math.random()) will cause many component instances
and DOM nodes to be unnecessarily recreated, which can cause
performance degradation and lost state in child components.
In your case, you are rendering the same component UIManager with different props so react can't identify which one to render that is the main reason behind
it's working after setting key
When you set key react identify them as a different component, though you could try the following approach
render () {
const finalView =
<UIManager json= { this.state.isLoading ? myLoadingComponentJSON : myFormJson} /> ;
return () {
finalView
}
}

What is the significance of keys in ReactJS?

I want to understand what happens if I don't use keys in dynamically added components. I removed keys and it renders without any issue and just gave warning messages regarding key usage. Would someone please give some example of what the consequences are if we don't use keys?
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity:
Example:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
TL;DR Use unique and constant keys when rendering dynamic children, or expect strange things to happen.
One of the tricky aspects I've found during the few weeks I've been using React.js is to understand the key property you're expected to pass to a component when it's part of an array of children. It's not that you have to specify this property, things will work most of the time apart from getting this warning on the console:
Each child in an array should have a unique "key" prop. Check the render method of undefined.
By reading the linked documentation it can be easy to not see the implications of this affirmation:
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
At first it looked to me it was all about performance but, as Paul O’Shannessy pointed, it's actually about identity.
The key here is to understand not everything in the DOM has a representation in React "Virtual DOM" and, because direct manipulations of the DOM (like a user changing an value or a jQuery plugin listening an element) are unnoticed by React, not using unique and constant keys will end up with React recreating the DOM node of a component when the key is not constant (and losing any untracked state in the node) or reusing a DOM node to render another component when the key is not unique (and tying its state to this other component).
Here you have a live demo showing how awful the results are:
http://jsfiddle.net/frosas/S4Dju/
Just add an item, change it, add more items and see what happens.
Also see
Source
Another useful usage of React keys other than creating dynamic elements is reseting elements when their keys change, for example in a project I had an <input/> element of type file and I wanted the element to be initialized to its initial value (no file chosen) each time the component renders, so I did the following:
Parent constructor:
this.state = {
fileInputKey: Date.now()
// other properties
};
The state object also had other properties, I just added this one for the sake of this example
Each time I wanted the input element in the child component be reset I did:
this.setState({fileInputKey: Date.now()});
Parent render:
<Child fileInputKey={this.state.fileInputKey}/>
Child render:
<input key={this.props.fileInputKey} type="file" onChange={this.onSelectFile}/>
Also see this example from React blog:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key

Categories

Resources