Conditionally wrapping React component without losing children state - reactjs

I was wondering if it's possible to conditionally wrap a React component without losing the state of the children.
In the sandbox, you can see when clicking the Increment button a few times, followed by the "Wrap children" or "Unwrap children" action, the counter is reset.
https://codesandbox.io/s/fervent-herschel-f62e2?file=/src/App.js
The relevant code can be seen here:
const ConditionalWrap = ({ condition, children }) => {
return (
<Fragment>
{condition ? (
<div>{children}</div>
) : (
children
)}
</Fragment>
);
};
Can anyone point me to a resource why this is happening? I guess the parent tree needs to stay the same, I'd just like to know why.
Is there some way to keep the state of all the children when conditionally wrapping them in an element or context provider?

There's explanations of the reconciliation in the react documentation.
React sees two elements of different types and so tears down the old and puts in a new.
There isn't a way to keep the state of all the children. It might be a good idea to either keep them wrapped in the same element. Or move the state into context, redux, or props that you pass down.
There is a library someone built for the purpose of reparenting: https://github.com/httptoolkit/react-reverse-portal

Related

In React, when a parent component re-renders, isn't it true that the children with unchanged props should not need to re-render?

I think the fact is that, when a parent component re-renders in React, in general, all the children components re-render as well.
I did one experiment to confirm:
https://codesandbox.io/s/currying-pine-r16rzi
return (
<div>
<div>Time now is {timeNow.toLocaleTimeString()}</div>
<TheTimeNow /><TheTimeNow />
</div>
);
This parent component re-renders, and <TheTimeNow /> has no change in props value (it has none), but it re-renders anyway and shows the updated time.
I think it is not the same to say, that React actually change the DOM, as I think the mechanism is that React use the previous virtual DOM to compare with the new virtual DOM, and update only the minimal actual DOM node as on document.documentElement as needed.
This can be seen if you use Google Chrome and do Inspect Element on the time statement: only the time portion changes and the other English words stay the same. Compare to this where all the nodes changes if it is plain JavaScript: https://jsfiddle.net/8sj2ugae/1/ when you do Inspect Element.
However, can't we argue that, if a child is <Foo /> and since there is no props passed to it, then <Foo /> really should not have changed, and it is wasteful to call Foo()? (if Foo is a functional component, or call the render() method if it is a class component.)
Now since all children re-renders, that means all their children also get re-rendered. Repeat this rule recursively, that would mean the whole subtree gets re-rendered.
My question is: is it true that they should not need to re-render if their props didn't change, or are there other factors that make them needing a re-render really?
The answer is yes, there is no need to rerender. But in order to know this we need to compare a new props with a previous props. And this operation is not free.
Also if we do a shallow comparison we are increasing the probability of bugs. Someone might be expecting rerender after a deep props update.
Should React.memo be the deault behaviour? Will it improve the performance in most cases? We don't know. There is no evidence for that. More on this in Mark Erikson's post.
Here is a great article by Dan Abramov: Before You memo(). I consider two points regarding your question:
In the future compiler might decide when to memoize a component.
Component rendering doesn't mean that all it's children will be rerendered.
Here is an example:
const ParentComponent = ({ children }) => {
const [state, setState] = useState(0);
return <div>{children}</div>
}
If we update the state children will not be rerendered. React just uses a previous value. Here is an article by Kent C. Dobbs about this optimization technique.
One last thing. React.memo and useMemo are different. The first one is for component memoization and the second one is a hook for expensive calculations. There are also shouldComponentUpdate for class components and PureComponent which compare not only props but also a state.
Hopefully the answer and links will shed some light on this topic.

Why do we need a `props` parameter in React?

I still don't get why we need a prop(s) in react, seriously. Why can't we just declare everything we need as an argument or parameter in the child component, then declare it, why do we have to declare it in a parent element then pass the props to the child component then catch it. I don't understand why. It seems a bit confusing, I'm yet to see the why in it
Props are useful in the case that you have a controller in the parent component and you want to pass the value of that controller to the child to make a certain action. The replacement for props would be to store everything globally in redux or mobx, but that needs much work. for example
const ParentComponent = () =>{
const [flag, setFlag] = useState(false)
return(
<div>
<button onClick={()=>setFlag(!flag)}>click me!</button>
<ChildComponent flagValue={flag}/>
</div>
)
}
as in the example for some reason the button that changes the flag is in the parent and you need to use that value in the ChildComponent. So here you benefit a lot from using props.
Also in writing a cleaner code and drier one so as not to repeat the same values in different react components
You might not be familiar with React if you ask such questions (no anger at all). It's one of the main concepts of the React library.
You can easily split a huge component into smaller pieces. But then, you need to provide the same data here and there. To prevent repeating yourself (DRY - don't repeat yourself), you can share the prop with many child components.
If you are interested in React - check the documentation.
It's one of the prettiest documentation I've ever read.
prop changes trigger UI re-render for the child component.
That's one of the props of using props.

React component state becomes invalid after change in parent component

function UserCard({ user }) {
let defaultUserName = user.nicknames[0];
let [selectedNickname, setSelectedNickname] = React.useState(defaultUserName);
// The selected nickname becomes invalid when the user is changed!!!
const selectedNicknameIsValid = user.nicknames.includes(selectedNickname);
return (<div>
<UserCardWithNickName user={user} selectedNickname={selectedNickname} />
<SelectNickName user={user} setSelectedNickname={setSelectedNickname} />
</div>);
In the above snippet the component's state holds the selected nickname from the list of user nicknames. But when the user changes in the parent component this component is re-rendered with the same state. So the nickname in the state is for the wrong user.
What is the preferred way to handle this? A lot of google searching couldn't find much discussion.
Am I doing this in some fundamental non-react way?
Is the preferred solution to use useEffect to fix the state when it becomes invalid as discussed here React.useState does not reload state from props?
Working example on codesandbox
Yeah, state changes in the parent component will usually re-render the child component, and hence reset the state of the child component. I'd suggest moving states that you want preserved up into the parent tree.
State management is an important concept in React. As your app grows, you'll realize doing this will add a lot of bloat and repetitive code. Learning about state management will prove very useful for you!
Yes. The link you have posted is one way to do it. The question you have to look for is if your child component is managing the value of selectedNickname in anyway. That is the only reason you would want to keep it as a seperate state in the child component. If all it does is read the props from the parent, simply use the props and do not maintain an internal state at all.
Months later I finally found a robust discussion of this problem in the react docs.
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
The problem I was trying to describe is called derived state.
I eventually solved it by adding a key when using the component. When a key changes, React will create a new component instance rather than update the current one. This is one of the recommended approaches in the docs.
<UserCard user={user} key={user.id} />

React setState on array of objects rerenders every component

I'm using React, and want to update array of objects in a state. The overall answer in internet was to use a similar approach to below:
const newState = this.state.elements.slice().concat({key: 'val'});
this.setState({elements: newState});
But the problem I encountered with this issue is that when these data are binded into a render function and components, it re-renders every component.
Example include (map is used from lodash to retrieve index while mapping):
render() {
return (
<div>
{map(this.state.elements, (el, index) => <Component key={`el-${index}`} el={el} />)}
</div>
);
}
Even though, the array order doesn't change and the key's of the components doesnt change, everytime the state changes, it re-renders and mounts the component from scratch.
Is there a possible best practice solution to this issue?
Best,
Y
Whats happening is that when you call setState the React Virtual Dom diffs state and calls render again. Render calls to a Map function which renders each component. Each component is thrown away and redrawn because the parent component changes.
This is normal and expected behavior by React. It's doing what you're asking of it, you changed state, it redraws the components.
But your question is how do I append to the list of components my new component without redrawing?
I think the issue is that you're relying on the unique key of react to be the index of the array item.
<Component key={`el-${index}`}
That will forever change every time you update state. What you need to do is use something like name, or string or a generated key or something from the data.
You are using just map function index value and of course after adding new element to the list will trigger a re-render because key value for each of them was changed.
if you did something like this:
<Component key={`el-${item.id}`}
Where item id is a constant but unique value, like a primary key in a database table. It would not redraw. The key should have a deterministic value.
See React Docs and this other post for more details.

Selective children component render

A basic question I need help here.
Whenever this.setState invoked at parent components, all the children components will be rendered. This will cause the performance issue if I have huge amount of child components.
Lets give an example,
Parent Component
handleToggleTick() {
const newObj = Object.assign({}, this.state, { iconName: ''});
this.setState({
iconName: newObj.iconName,
});
}
render() {
return(
<ChildComponentA iconName={this.state.iconName} toggleTick={() => this.handleToggleTick}></ChildComponentA>
<ChildComponentB></ChildComponentA>
<ChildComponentC></ChildComponentA>
)
}
Based on the example above, whenever handleToggleTick invoked from childcomponentA, setState invoked for new iconName. What I want is, only ChildComponentA only the one get render since props.iconName is related to it, but not for childcomponentB and childcomponentC.
I understand there is an option to check shouldComponentUpdate in childcomponent to prevent it get render. But, imagine I have over 100 of childcomponent, would it be frustrating to write over 100 times of shouldComponentUpdate method?
I need help here, please advice!
React doesn't provide any way to render children selectively. The component will either render or not. But I need to highlight a few points why this is not a problem when we use React in practice.
First of all, you don't need to manually implement shouldComponentUpdate for each component. If you don't want to rerender component if its props and state haven't changed, you can just extend from the PureComponent class instead of the Component class. Note that React.PureComponent's shouldComponentUpdate() only uses shallow comparison for state and props. But this shouldn't be a problem if you follow react best practices and avoid mutating the state.
Also, it's not practical to have more than 100 different components in one render method. React always encourages decomposing your UI into smaller components and using component composition. When we follow this approach, components will be nested inside each other in different levels instead of having a large number of components in one render method.
What I'm trying to explain is it's more practical and easy to manage when we compose our component in a nested fashion (2) rather than having lots of components inside a big container component (1).
In your example, if ChildComponentB and ChildComponentC are inside another component called ChildConatainerComponent then we only need to implement shouldComponentUpdate() for ChildConatainerComponent. Then it will automatically stop rendering any child element inside it.
render() {
return(
<ChildComponentA iconName={this.state.iconName}
toggleTick={() => this.handleToggleTick}/>
<ChildConatainerComponent/>
)
}
class ChildConatainerComponent extends PureComponent {
render() {
return (
<div>
<ChildComponentB/>
<ChildComponentC/>
</div>
);
}
}
Another very import concept to keep in mind is calling render function doesn't mean that React recreates all the DOM elements again. The render method only make changes to React virtual DOM which is an in-memory representation of DOM and it's faster than actual DOM. Then React compare versions of virtual DOM which are before the update and after the update and the actual DOM will be updated with only what has actually changed.
Another solution you could consider is moving iconName into ChildComponentA, considering this is the only component related to it.

Resources