Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Prevent Child Component Re-Render when Parent state changes

I have two stateless components and one stateful component. In my stateful component, I have a list of people and when I click one of them, it opens a modal. I basically store modal's state in stateful component and send the values as props to modal. So it changes states anytime it opens a model and re-renders everything.

How can I prevent my stateless classes to be re-rendered after I change state?

I have tried memo in my stateless components but no luck

Modal component

const modal = React.memo((props) => {

    return (
    <Modal show={props.show} onHide={props.handleClose} aria-labelledby="contained-modal-title-vcenter"
           centered>
        <Modal.Header closeButton>
            <Modal.Title>Modal heading</Modal.Title>
        </Modal.Header>
        <Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
        <Modal.Footer>
            <Button variant="secondary" onClick={props.handleClose}>
                Close
            </Button>
        </Modal.Footer>
    </Modal>
    )
});

export default modal

Person Component

const person = React.memo((props) => {
    console.log("render");
    return (
        <article className="Person" onClick={props.clicked}>
            <Container>
                <Row>
                    <Col>
                        <p style={{marginBottom: 0 + 'px', marginTop: 1 + 'rem'}}><b>{props.name}</b></p>
                        <div className="Organization-wrapper">
                            <FontAwesomeIcon className="Organization-icon" icon={faBuilding}/><p>{props.company}</p>
                        </div>
                    </Col>
                    <Col>
                        <div>
                            {
                                props.image ?
                                    <img src={props.image} height="50" width="50" className="Person-image"
                                         alt="avatar"/>
                                    : <svg height="50" width="50" className="Person-image">
                                        <rect fill="cornflowerblue" x="0" y="0" height="50" width="50"/>
                                        <text
                                            fill="#ffffff"
                                            fontSize="20"
                                            textAnchor="middle"
                                            x="25"
                                            y="30">{props.first_char.toUpperCase()}</text>
                                    </svg>
                            }
                        </div>
                    </Col>
                </Row>
            </Container>
        </article>
    );
});

Stateful Component

class PersonList extends Component {

    state = {
        persons: [],
        show: false
    };


    componentDidMount() {
        axios.get('')
            .then(response => {
                const result = response.data.data.slice(0, 5);
                this.setState({persons: result});
            });
    }

    personSelectedHandler = (id) => {
        this.showModal();
    };

    showModal = () => {
        this.setState({ show: true });
    };

    hideModal = () => {
        this.setState({ show: false });
    };

    render() {
        const persons = this.state.persons.map(person => {

            let avatar;
            if (person.picture_id != null) {
                avatar = person.picture_id.pictures["128"];
            }
            return <ListGroup>
                <ListGroup.Item>
                    <Person
                        key={person.id}
                        name={person.name}
                        company={person.org_id.name}
                        first_char={person.first_char}
                        image={avatar}
                        clicked={() => this.personSelectedHandler(person.id)}
                    />
                </ListGroup.Item>
            </ListGroup>
        });

        return (

            <div className="Wrapper">
                <Modal show = {this.state.show} handleClose = {this.hideModal}/>
                <Header/>
                <div className="Breadcrumb">
                    <h4 className="Breadcrumb-text"><b>People's List</b></h4>
                    <hr/>
                </div>
                {persons}
            </div>
        );
    }

}

export default PersonList;
like image 810
wolf Avatar asked Oct 15 '25 04:10

wolf


1 Answers

<Person
    ...
    clicked={() => this.personSelectedHandler(person.id)}
/>

This will define a new function reference for props.clicked on each re-render of the parent function. This will cause the props for Person to be changed on each render, causing Person to also re-render.

Since you're not currently using id in personSelectedHandler, you could just omit the argument and keep the function reference constant:

clicked={this.personSelectedHandler}

If you end up needing the id context, you might want to consider some use of the parents' state to achieve what you're after instead, if you really want to avoid re-rendering.

You could also convert Person to a (pure) class component, and handle the callback from within that component.

Lastly, if push comes to shove and you absolutely must use this in-line arrow function, you could override React.memo's comparison function, which is roughly analogous to defining componentShouldUpdate for class components. From the docs:

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

There might be another way I'm not aware of to make this work, but hopefully this helps you understand the issue to explore alternate solutions. There's also some ideas and discussion here: Why shouldn't JSX props use arrow functions or bind?

like image 193
Dylan Walker Avatar answered Oct 17 '25 18:10

Dylan Walker