Ad

Using ReactDOM.render Inside An Event Handler To Avoid Rendering Expensive Components

I have a React app with a list of tasks which each have due dates. I want the user to be able to click the due date to pop up a datepicker to select a new one.

I am using the material UI library http://www.material-ui.com/#/components/date-picker

I could simply render a datepicker on each task in the list but it seems like it might cause performance problems on a long list to render such a complex component on each item. I would prefer to just display the due date and only render the datepicker when the user clicks to change the due date.

Here is the solution I have come up with:

TaskFooter = React.createClass({
    propTypes: {
        task: React.PropTypes.object.isRequired
    },
    onDateChanged(e, newDate){
        // handle data update
    },
    showDatePicker(){
        let dp = ReactDOM.render(
            <MUI.DatePicker autoOk={true} textFieldStyle={{display: 'none'}} onChange={this.onDateChanged} />,
            this.refs.datePickerContainer
        );
        dp.openDialog();
    },
    render() {

        return (
                <div onClick={this.showDatePicker}>
                    <MUI.Libs.SvgIcons.ActionEvent />
                    {this.props.task.duedate}
                    <div ref="datePickerContainer"></div>
                </div>
        );
    }
});

This seems to work, but I want to know if this pattern is the "correct" way to do this. Is there a better way to dynamically render potentially expensive react components in response to an event?

Edit: I have found another way to do this which seems more "react" but I am not sure if it is entirely correct.

TaskFooter = React.createClass({
    propTypes: {
        task: React.PropTypes.object.isRequired
    },
    getInitialState(){
        return {
            showDatePicker: false
        }
    },
    componentDidUpdate(prevProps, prevState){
        if(this.state.showDatePicker && !prevState.showDatePicker){
            this.refs.datePicker.openDialog();
        }
    },
    onDateChanged(e, newDate){
        console.log('new date', newDate);
        this.setState({
            showDatePicker: false
        });
    },
    showDatePicker(){
        this.setState({
            showDatePicker: true
        });
    },
    render() {

        return (
                <div onClick={this.showDatePicker}>
                    <MUI.Libs.SvgIcons.ActionEvent />
                    {this.props.task.duedate}
                    { this.state.showDatePicker ?
                        <MUI.DatePicker autoOk={true} textFieldStyle={{display: 'none'}} onChange={this.onDateChanged} ref="datePicker" />
                    : ''}
                </div>
        );
    }
});

This correctly shows and hides the datepicker, but when setting the closed state I get the following warning in my console:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the DatePickerDialog component.

Despite this warning it seem the state is correctly set on my component. Should I ignore this warning? Is my new pattern correct?

Ad

Answer

Only use ReactDOM.render() when mounting your React app. It should never be used within a React component itself.

Familiarise yourself with how state works. I suggest the following:

TaskFooter = React.createClass({
    propTypes: {
        task: React.PropTypes.object.isRequired
    },
    getInitialState() {
        return {
            displayDatePicker: false
        };
    }
    onDateChanged(e, newDate) {
        // handle data update
    },
    showDatePicker() {
        this.setState({
            displayDatePicker: false
        });
    }
    componentDidUpdate(prevProps, prevState) {
        // this is a little bit hacky,
        // ideally there would be a prop for DatePicker to instruct it to already be open
        if (this.state.displayDatePicker && !prevState.displayDatePicker) {
            this.refs.dp.openDialog();
        }
    },
    render() {

        return (
            <div onClick={this.showDatePicker}>
                <MUI.Libs.SvgIcons.ActionEvent />
                {this.props.task.duedate}
                {this.state.displayDatePicker ? (
                    <MUI.DatePicker
                        ref="dp"
                        autoOk={true}
                        textFieldStyle={{display: 'none'}}
                        onChange={this.onDateChanged}
                    />
   Change={this.onDateChanged}
                    />
                ) : null}
            </div>
        );
    }
});

This achieves the same thing in a cleaner fashion. The state variable displayDatePicker determines whether or not the datepicker shows. I've also used componentDidUpdate() to detect when the datepicker has just been activated.

One further thing however. I think you should question your assumption that displaying the datepicker by default would necessarily create performance problems. Do you know that to be the case? I fear you might be trading an increase in code complexity for a trivial performance boost.

Ad
source: stackoverflow.com
Ad