Skip rendering children in React - javascript

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!

Related

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.

Does React rerender all the components of a FOR loop if the array changes but some of the keys remain the same?

I'm implementing infinite scroll of videos. Each component has a unique id im using as the key. Each component will be very large so i want to only keep a couple items on the DOM at a time. For example if i have 5 elements with id A,B,C,D,E and next state is B,C,D,E,F will B,C,D,E be rerendered by React?
Thanks for your help!
Depends what you mean with re rendered, re generate jsx or re creating and replacing the DOM elements.
The jsx is re created when:
When the parent creates new jsx and the child is a functional component (not wrapped in React.memo) the child will re create jsx because functional components always re create jsx.
When the parent re creates jsx and passes the child different prop values than the previous render.
When value in [value,setValue]=useState() or when this.state changes (when state changes).
When value in value = useContext(someContext) changes.
In most cases when value in value = useCustomHook() changes but this is not guaranteed for every hook.
When Parent renders and passes a different key prop to Child than the previous render (see 2). This causes the Child to unmount and re mount as well.
Creating new jsx does not mean that DOM elements are recreated, after creating jsx (virtual dom) React will compare this to the actual DOM and only update actual DOM if it changed. This compare cycle can be optimized by memoizing the jsx using React.memo, React.useMemo and reselect.
With props changing value between renders I mean that the reference has changed for objects or value for primitives. So propName={{new:'object'}} and propName={{new:'object'}} will be different. The virtual dom (jsx) created will likely be the same as the DOM so the component will not actually render. But when you do something like onClick={new=>'function'} then virtual dom compare will always fail, that is why useCallback is used.
will B,C,D,E be rerendered by React?
Technically yes, but for enhanced performance React uses a virtual DOM, when state changes occur, the virtual DOM is updated and the previous and current version of virtual DOM is compared, finally React sends a batch update to the real DOM to update the UI. Meaning that it will render only the elements that had changed into the real DOM.

React component not rerendering when props changes

Here are my 2 React components - Parent and Child
class ParentComponent extends Component{
render(){
/* consultations comes from redux store
and looks like { consultation_id:123, date:10/12/2013 } */
return(
_.map(this.props.consultations,(consultation)=>{
return{
<ChildComponent consultation={consultation} />
}
})
);
}
}
class ChildComponent extends Component{
render(){
return(
<div>this.props.consultation.date</div>
);
}
}
My problem is that I have certain actions that modify the consultations object. For eg: the props.consultation.date changes in the parent component, but the child component does not re render to show the latest consultation date.
However, I noticed that if I send each item in the consultations object to the child component like <ChildComponent date={this.props.consultation.date} /> it rerenders when the date changes!
Any idea why React does not re-render components when props sent as an object change?
I could always do with the work around but wondering if this is a bug or am I missing something?
The solution is to change consultation={consultation} to consultation={...consultation}. I am still unsure why, but it works!
You should definetly add the key prop with consultation_id to the child element. React can have problems rerendering elements without a key prop!
When you map over an array of items, you need to pass a unique key prop to each item. This signals to react which element is which.
...
_.map(this.props.consultations, consultation => {
return (
<ChildComponent
key={consultation.id}
consultation={consultation}
/>
)
})
The details you specified says that you change the date of same object~consultation in parent component and you parent component is getting the data as props.
Now if you mutating the same consultation object it won't make component re-render.
consultation.date = /* some other date */;
it'll not re-render the component.
But if you change the reference of the object like:
newConsulationData = { ...consultation }
newConsultationData.date = /* some other date */;
It'll work fine.
In your scenario you might have trouble as you directly mutating the props array object, and passing the same array so I suggest you change the reference of consultations array:
You'll need to
newConsulations = [ ...consultations ];
newConsultations[index of consultation].date = /* some other date */;
This should solve your problem.
When working with react try not to mutate the objects if you want to re-render the component on the changes .
I think it may be related to data type.
The first approach is passing an Object to child component,
<ChildComponent consultation={consultation} />
the second approach is passing a String to child component.
<ChildComponent date={this.props.consultation.date} />
When the property date of the object consultation is changed, the object's index is not changed. I think react is referencing an object by its index. Object returns an index not the exact value. The index is the specific address where object is stored in memory. But a string or boolean or number returns the value directly.
I think react is comparing object by index, and comparing string by the value. In the first approach, index does not update, so re-rendering is not happening.
The third approach extracts the object properties with spread symbol.
<ChildComponent consultation={...consultation} />
I think react is referencing the property itself in this approach, as each property is extracted. It's no longer referencing the object, so re-render works.
(I used a few I think statement here, as it's just my guess. Still need official documents to support.)

React.cloneElement: pass new children or copy props.children?

I'm confused by the third "children" parameter of React.cloneElement and it's relation to this.props.children.
I followed this guide on higher order components and have the following code:
render() {
const elementsTree = super.render()
let myPropChange = {}
/* work on newProps... */
myPropChange.something = "nice!".
const newProps = Object.assign({}, elementsTree.props, myPropChange)
/* map children */
const newChildren = React.Children.map(elementsTree.props.children, c => something(c))
return React.cloneElement(elementsTree, newProps, newChildren)
}
Should I put the mapped children into my newProps.children or should I pass them as the third parameter to cloneElement?
Object.assign copied the children from props to newProps anyway, should I skip them?
In the guide it says
Components don’t have a guaranty of having the full children tree resolved.
What does that mean in my situation? That this.props.children is not there?
Added 4th question: Why should I clone the props at all and not just directly edit them?
Should I put the mapped children into my newProps.children or should I pass them as the third parameter to cloneElement?
Either should be fine.
Object.assign copied the children from props to newProps anyway, should I skip them?
When using cloneElement, you don't need to copy the props yourself. You can just do React.cloneElement(elementsTree, {something: 'nice!'}).
In the guide it says "Components don’t have a guaranty of having the full children tree resolved." What does that mean in my situation? That this.props.children is not there?
I can't be sure what that article meant, but I believe the author means that your component can choose not to use this.props.children. For example, if you render <Foo><Bar /></Foo>, then Foo will receive <Bar /> in this.props.children, but if Foo doesn't use this.props.children in its render method, then Bar will never be rendered.
Why should I clone the props at all and not just directly edit them?
React elements are immutable and there's no way you can change the props after an element is created. This allows some performance optimizations in React which wouldn't be possible otherwise.

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

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.

Categories

Resources