4

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;
wolf
  • 399
  • 1
  • 6
  • 19

2 Answers2

3
<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?

Dylan Walker
  • 897
  • 9
  • 9
  • Thanks for the answer. I changed my stateless function component to React.Component and used shouldComponentUpdate in it. I couldn't make it work with other options – wolf Apr 19 '19 at 09:45
  • No problem, it's kind of an annoying use-case to deal with. If you want to continue using functional components with `React.memo`, I clarified my answer for overriding React.memo's comparison function. It should be approximately the same as defining `componentDidUpdate` (although it's expected to return the inverse; `true` if the component _shouldn't_ re-render). I've never used it for this particular case though, and am not sure if there are any pitfalls I'm not aware of. – Dylan Walker Apr 19 '19 at 16:21
2

if you use hook you can use useCallback() method with Recat.memo together. in your componenet :

const callBack = useCallback(() => this.personSelectedHandler(person.id),[person.id]);

useCallback((person.id) => personSelectedHandler(person.id),[person.id]); always returns the same function instance as long as person.idis the same. then:

<Person
    ...
    clicked={callBack}
/>
sably
  • 159
  • 1
  • 8