this.props.children not re-rendered on parent state change - reactjs

I have a piece of code
import React, {Component} from 'react';
class App extends Component {
render() {
return (
<Container>
<Child/>
</Container>
)
}
}
class Container extends Component {
render() {
console.log('Container render');
return (
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
)
}
}
class Child extends Component {
render() {
console.log('Child render');
return <h1>Hi</h1>
}
}
export default App;
When clicking on 'Hi' msg, only Container component keeps re-rendering but Child component is not re-rendered.
Why is Child component not re-rendered on Container state change?
I would reason, that it doesn't happen due to it being a property of Container component, but still this.props.child is evaluated to a Child component in JSX, so not sure.
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
Full example https://codesandbox.io/s/529lq0rv2n (check console log)

The question is quite old, but since you didn't seem to get a satisfying answer I'll give it a shot too.
As you have observed by yourself, changing
// Scenario A
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
to
// Scenario B
<div onClick={() => this.setState({})}>
<Child />
</div>
will in fact, end up with
Container render
Child render
in the console, every time you click.
Now, to quote you
As fas as I understand, if setState() is triggered, render function of
Container component is called and all child elements should be
re-rendered.
You seemed to be very close to understanding what is happening here.
So far, you are correct, since the Container's render is executed, so must the components returned from it call their own render methods.
Now, as you also said, correctly,
<Child />
// is equal to
React.createElement(Child, {/*props*/}, /*children*/)
In essence, what you get from the above is just an object describing what to show on the screen, a React Element.
The key here is to understand when the React.createElement(Child, {/*props*/}, /*children*/) execution happened, in each of the scenarios above.
So let's see what is happening:
class App extends Component {
render() {
return (
<Container>
<Child/>
</Container>
)
}
}
class Container extends Component {
render() {
console.log('Container render');
return (
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
)
}
}
class Child extends Component {
render() {
console.log('Child render');
return <h1>Hi</h1>
}
}
You can rewrite the return value of App like this:
<Container>
<Child/>
</Container>
// is equal to
React.createElement(
Container,
{},
React.createElement(
Child,
{},
{}
)
)
// which is equal to a React Element object, something like
{
type: Container,
props: {
children: {
type: Child, // |
props: {}, // +---> Take note of this object here
children: {} // |
}
}
}
And you can also rewrite the return value of Container like this:
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
// is equal to
React.createElement(
'div',
{onClick: () => this.setState({})},
this.props.children
)
// which is equal to React Element
{
type: 'div',
props: {
children: this.props.children
}
}
Now, this.props.children is the same thing as the one included in the App's returned React Element:
{
type: Child,
props: {},
children: {}
}
And to be exact, these two things are referentially the same, meaning it's the exact same thing in memory, in both cases.
Now, no matter how many times Container get's re-rendered, since its children are always referentially the same thing between renders (because that React Element was created in the App level and it has no reason to change), they don't get re-rendered.
In short, React doesn't bother to render a React Element again if it is referentially (===) equal to what it was in the previous render.
Now, if you were to change the Container you would have:
<div onClick={() => this.setState({})}>
<Child />
</div>
// is equal to
React.createElement(
'div',
{onClick: () => this.setState({})},
React.createElement(
Child,
{},
{}
)
)
// which is equal to
{
type: 'div',
props: {
children: {
type: Child,
props: {},
children: {}
}
}
}
However in this case, if you were to re-render Container, it will have to re-execute
React.createElement(
Child,
{},
{}
)
for every render. This will result in React Elements that are referentially different between renders, so React will actually re-render the Child component as well, even though the end result will be the same.
Reference

The <Child /> component is not re-rendered because the props have not changed. React uses the concept of Virtual DOM which is a representation of your components and their data.
If the props of a component do not change the component is not re-rendered. This is what keeps React fast.
In you example there are no props sent down to Child, so it will never be re-rendered. If you want it to re-render each time (why would you ?), you can for example use a pure function
const Child = () => <h1>Hi</h1>;

Change {this.props.children} to <Child /> in Container component, (now you can remove <Child /> from App component).
If you are clicking the div you will get both the 'Child render' and 'Container render' in console.
(In this example your child is static component. Then there is no point for the re-rendering.)

Related

Re-render React descendant components without re-rendering ancestors

I'm trying to build a Navbar using React 16.8.3. I would like to use composition to pass the Navbar content instead of passing a config object via props, in order to have more flexibility. Something like this:
<Navbar>
<NavItem>Some label</NavItem>
<NavItem>
<span>Some arbitrary content</span>
<NavItem>
</Navbar>
instead of:
const navItems = [
{
label: 'Some label'
},
{
label: 'Some other label'
}
]
<Navbar items={navItems} />
So far the Navbar is working fine. I've added some logic in the shouldComponentUpdate method to prevent multiple re-renders:
shouldComponentUpdate(nextProps) {
return nextProps.selectedItem !== this.props.selectedItem;
}
so the Navbar only re-renders when its selected item changes, and not, for instance, when the Navbar parent re-renders.
Problem is that one NavItem contains a badge with a task count that must be updated whenever the user does some tasks:
Todos screenshot
and the item markup is:
<Navbar>
<NavItem>
<div className="has-badge">
<span>Label</span>
<span className="badge">{this.props.toDoCount}</span>
</div>
</NavItem>
</Navbar>
this.props.toDoCount is a prop of the Navbar parent, and not of the Navbar itself.
How can I update the badge number without re-rendering the whole Navbar?. So far I've tried creating a Badge component, adding some state, and a method to update the badge number using a ref in the Navbar parent:
import React, { PureComponent } from 'react';
interface BadgeProps {
number: number;
}
class Badge extends PureComponent<BadgeProps> {
state = {
number: 0
};
setCount(number) {
this.setState({
number
});
}
render() {
return <span className="badge">{this.state.number}</span>;
}
}
In the Navbar parent:
private todos = createRef<Badge>();
...
componentDidUpdate(prevProps: EhrProps) {
this.todos.current.setCount(toDosCount);
}
and it's working, but... is there an easier or cleaner way of doing this in React??
Thanks!
PS: We are using Redux in the project, but I would like to avoid using the store in the Navbar or its items.
EDIT:
I'm using React.children and React.cloneElement in the Navbar's render method:
render() {
const { className, children, selectedItem, ...rest } = this.props;
const classes = classNames(
{
navbar: true
},
className
);
return (
<nav className={classes} {...rest}>
{React.Children.map(children, child => {
if (child.type === NavItem) {
return React.cloneElement(child, {
onClick: this.handleItemClick,
selected: child.props.name === selectedItem
});
}
return child;
})}
</nav>
);
}
And each NavItem handles its own render:
return (
<div className={classes} onClick={handleClick} onKeyPress={handleKeyPress} role="menuitem" tabIndex={0}>
{children}
</div>
);
Presumably, you have some code for the the Navbar component that looks a bit like this.
class Navbar extends React.Component<Props> {
render() {
return (
<div>
{this.props.navItem.map(item => <NavItem key={item.label}>{item.label}</NavItem>)};
</div>
);
}
}
and then some code to render each child NavItem.
To make the component fairly efficient, it's sufficient to have the whole of Navbar rerender, but only not re-render each child.
What I would recommend is:
Make each child of Navbar be rendered in its own component; in the component above it's called NavItem
Use either componentShouldUpdate or React.PureComponent (look into this! Once you understand it, it's a great general solution to use by default instead of React.Component for every component) to make sure that each child only re-renders when its value changes
What will happen when you update the badge for the single NavItem is that Navbar will re-render. Most of the NavItems will see their Props haven't changed, and not re-render. The single child of Navbar that has the badge will have changed, and will re-render. With this, the real overhead is actually quite low.
If your Navbar has a ton of children or your badge for that single child changes a lot, you can probably optimize it more by using React.Context or Redux to pass in the value for that single child, but that feels messy and seems like premature optimization.
Good luck!

Get the child's props onClick in parent component

I have a parent ButtonGroup component and the child buttonItem component:
//ButtonGroup Component (Parent)
clicky(){
//capture the props of the clicked button, ie, caption and disabled here.
}
render() {
return (
<div onClick={this.clicky}>
{this.props.children}
</div>
)
}
//buttonItem component:
render() {
return (
<button disabled={this.props.disabled}>{this.props.caption}</button>
)
}
//final render
<ButtonGroupComponent>
<buttonItem caption="Nothing"/>
<buttonItem caption="Something" disabled={true}/>
<buttonItem caption="Refresh"/>
</ButtonGroupComponent>
from the above code is there any way i can capture the props of the clicked child buttonItem?
In your case, you need to merge this.props.children with your custom prop. So, I suggest you use React.Children to operate with it.
By the way, after adding new prop you need to return this child, so cloneElement will help you with this.
Inside import section of ButtonGroupComponent:
import React, { Children, Component, cloneElement } from 'react';
Its render function will look like this:
render() {
const childrenWithCustomHandler = Children.map(this.props.children, itemChild => {
// Arguments of cloneElement (component, [customProps], [customChildren])
return cloneElement(itemChild, { onClickItem: this.clicky })
}
);
return <div>{childrenWithCustomHandler}</div>;
}
And the code of buttonItem component will look like:
return (
<button
disabled={this.props.disabled}
onClick={() => {
this.props.onClickItem({ ...this.props });
}}
>
{this.props.caption}
</button>
)
I used Spread operator to clone the object, so if you will want to change props in your clicky function, the child won't be rendered.

Using React, is it possible to define a component inside the definition of another component?

(this question differs to 'is it possible to use a component inside another', this question is 'can I define a component inside the definition of another', it is not a duplicate of 'Can I write Component inside Component in React?')
Is it possible to define a component inside the definition of another component? This way I can use the props of the outside component in the inner component. It would keep the code more concise. Something like the following...
class AComponent extends Component {
CustomButton = (props) => {
let { disabled, ...otherProps } = props // <-- props of the inner component
const {isDisabled, currentClassName} = this.props // <-- props of the main component
return (
<button
className={className}
disabled={isDisabled}
{...otherProps}>
</button>
)
}
render() {
return (
<div>
<CustomButton>Add something</CustomButton>
<CustomButton>Delete something</CustomButton>
<CustomButton>Edit</CustomButton>
</div>
)
}
}
If the custom button was defined on its own (the usual way of defining components) I would have to do something like below which is ok but more verbose and less dry as I repeat the definition of {...buttonProps} for each component
let buttonProps = {
className: this.props.currentClassName,
disabled: this.props.disabled
}
return (
<div>
<button {...buttonProps}>Add something</button>
<button {...buttonProps}>Delete something</button>
<button {...buttonProps}>Edit</button>
</div>
)
While yes, it's possible to define one function component inside another function component, this is not recommended.
Referring to the ReactJs docs:
reactjs.org/docs/components-and-props
reactjs.org/docs/conditional-rendering
Most, if not all, examples show child components being defined outside of the parent component.
Defining a component inside another will cause the child component to be re-created on mount and unmount of the parent, which could cause unexpected behavior if the child is using props from the parent, and cannot handle if those props are suddenly undefined.
It's best to define components separately.
Yes! I needed to define the function component in the render method...which makes sense
class AComponent extends Component {
render() {
const ButtonLocal = (props) => {
const {isDisabled, currentClassName} = this.props
return (
<Button
disabled={isDisabled}
className={currentClassName}
{...buttonProps}>
</Button>
)
}
return (
<div>
<ButtonLocal>Add something</ButtonLocal>
<ButtonLocal>Delete something</ButtonLocal>
<ButtonLocal>Edit</ButtonLocal>
</div>
)
}
}
Very much possible to add components inside a parent component. Consider following example:
<ParentComponent style={styles}>
<ChildComponent style={styles} {...props} />
</ParentComponent>

Render a component when another component is clicked

I want to render BlackSpark when RedSpark is clicked, but I'm not sure how to change the state of a component in another component. I know how to set state in the component itself, but how do I affect another component when I click a different component?
class BlackSpark extends React.Component {
render() {
return (
<div className="black"></div>
);
}
}
class RedSpark extends React.Component {
render() {
return (
<div className="red"></div>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<BlackSpark />
<RedSpark />
</div>
);
}
}
In React, there's a concept of component composition as you've already embraced -- it allows you to accomplish what you want by rendering children based on the parent's state, another key concept known as lifting state up. What this means, is if you have mutually dependent components, create a single parent which composes them, and have state in the parent control the presentation and logic of the children. With the parent App, you can keep your state inside App, and based on App's state, conditionally render whatever you want -- either BlackSpark or both. For example, using the logical && operator:
{condition && <Component />}
This will only render <Component> when condition is truthy, or else it will not render anything at all (except for when condition is 0). Applying it to this situation, try adding state to your App component to utilize conditional rendering.
There's another key concept you need to understand: component props. They are essentially inputs to a component, certain properties passed to the component to tell how it should behave -- like attributes on regular HTML elements such as input placeholders, URLs, and event handlers. For example:
<Component foo="bar" bar={3} />
This will pass the props foo and bar down to Component with the values "bar" and 3 respectively and are accessible through this.props. If you were to access this.props.foo inside the Component component it would give you "bar". If you pair this up with composition, you can accomplish what you want:
class Example extends React.Component {
constructor() {
super();
this.state = {
showHello: true
}
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.setState(prevState => ({
showHello: !prevState.showHello
}));
}
render() {
return (
<div>
{this.state.showHello && <Child2 />}
This is a test.
<Child1 onClick={this.handleChange} />
</div>
);
}
}
class Child1 extends React.Component {
render() {
return <div onClick={this.props.onClick}>Click me!</div>
}
}
class Child2 extends React.Component {
render() {
return <div>Hello!</div>
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
The above example lifts state up by having a parent compose the children and maintain the state. It then uses props to pass down an onClick handler to Child1, so that whenever Child1 is clicked, the state of the parent changes. Once the state of the parent changes, it will use conditional rendering to render <Child2> if the condition is truthy. Further reading at the React documentation and on the logical && operator.
I know how to set state in the component itself, but how do I affect another component when I click a different component?
The recommended way to do it would be to create a parent component that has the state. You'd then use that state to determine when to render the other child component.
I want to render BlackSpark when RedSpark is clicked, but I'm not sure how to change the state of a component in another component. Also, what if I want to hide BlackSpark when GreenSpark is clicked and GreenSpark is inside BlackSpark?
In this case, here's how you'd do it.
const GreenSpark = ({ onClick }) => (
<button className="green" onClick={onClick}>X</button>
)
const BlackSpark = ({ onClick }) => (
<div className="black">
<GreenSpark onClick={onClick} />
</div>
)
const RedSpark = ({ onClick }) => (
<div className="red" onClick={onClick}></div>
)
class Spark extends React.Component {
constructor(props) {
super(props)
this.state = {
showBlack: false
}
this.boundShowBlack = this.showBlack.bind(this)
this.boundHideBlack = this.hideBlack.bind(this)
}
showBlack() {
this.setState({ showBlack: true })
}
hideBlack() {
this.setState({ showBlack: false })
}
render() {
return (
<div>
<RedSpark onClick={this.boundShowBlack} />
{this.state.showBlack && <BlackSpark onClick={this.boundHideBlack} />}
</div>
)
}
}

How can I pass props to children of React Router?

I have checked this link
So far, I'm not able to understand the handler part. So I was hoping for a more simple example perhaps?
Here is my main parent component:
class appTemplate extends React.Component {
render() {
return (
<div>
<Header lang={this.props.route.path}/>
{this.props.children}
<Footer lang={this.props.route.path}/>
</div>
);
}
}
What I want to do is pass down the prop this.props.route.path to my child components which is this.props.children.
I'm not really fully familiar with all the terms even though I've been touching React already for the last few months.
An example with a proper explanation would be greatly appreciated. Thanks.
The proper way to achieve that is using React.Children.map()
class appTemplate extends React.Component {
render() {
return (
<div>
<Header lang={this.props.route.path}/>
{React.Children.map(
this.props.children,
child => React.cloneElement(child,
{
customProp: "Here is your prop",
path: this.props.route.path
})
)}
<Footer lang={this.props.route.path}/>
</div>
);
}
}
React has a cloneElement function. The idea is to clone the children object, passing on path as a part of the props:
class appTemplate extends React.Component {
render() {
let children = null;
if (this.props.children) {
children = React.cloneElement(this.props.children, {
path: this.props.route.path
})
}
return (
<div>
<Header lang={this.props.route.path}/>
{children}
<Footer lang={this.props.route.path}/>
</div>
);
}
}
You should then be able to access the path using this.props.path within a child element, but (from what I remember) not from within elements nested within the child.
You can read more about cloning and passing values here:
https://facebook.github.io/react/docs/top-level-api.html#react.cloneelement

Resources