0
votes

I've asked for more information about the error here: Should I ignore React warning: Input elements should not switch from uncontrolled to controlled?. But I'm still stuck..

So I have a case where I want to switch a time input field from holding it's own value to taking over a value from another time input field. I'm doing this using a simple 'locked' / 'unlocked' button. Clicking on that button allows me to choose whether the input field is locked (takes over value from others) or unlocked (keeps its own value).

I'm doing this inside a loop because each time field will be repeated 7 times (for each day).

Problem

However, each time I switch from regular time input field to a time input field with actions behind it, React's giving me the following error:

A component is changing an uncontrolled input of type time to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component

Explaining the code below

The part between comments goes as follow: If the locked state of the current day input field is 'true', then the input must take a value from a state and execute a onchange handler. If it is false, it shouldn't do anything and react as a basic input field.

What I have

export class OpeningHoursTable extends Component{
    constructor(props) {
        super(props);
        this.state = {
            sharedInputMo : '',
            empty: undefined
        };
    }
    render() {
        const Days = [{id: 1, day: 'Monday'},{id: 2, day: 'Tuesday'},{id: 3, day: 'Wednesday'},{id: 4, day: 'Thursday'},{id: 5, day: 'Friday'},{id: 6, day: 'Saturday'},{id: 7, day: 'Sunday'}];
        const Table = Days.map(({id, day}) => (
            <div key={id} className='ohTableRow'>
                <div className='ohTableMorning'>
                    <div className='ohTableContentBlock'>{day}</div>
                    <div className='ohTableContentBlock'>
// THIS IS THE PART THAT GIVES ME THE ERROR
                         <input type='time' 
                                value={this.state['lock' + day + '_mo'] === true ? this.state.sharedInputMo || this.state.empty: this.state.empty} 
                                onChange={this.state['lock' + day + '_mo'] === true ? thisInput => this.setState({sharedInputMo : thisInput.target.value }) : null } 
                         />
// THIS IS THE PART THAT GIVES ME THE ERROR
                     </div>

                    <div className='ohTableLockState' onClick={this.state['lock' + day + '_mo'] === true ? () => this.setState({ ['lock' + day + '_mo'] : false }) : () => this.setState({ ['lock' + day + '_mo'] : true }) }>
                        {this.state['lock' + day + '_mo'] === true ?
                            <Icon name='Locked' />
                        :
                            <Icon name='Unlocked' />
                        }
                    </div>

                </div>
            </div>
        ));
        return (
            <div className='ohTable'>
                {Table}
            </div>
        );
    }
}
2

2 Answers

0
votes

The root of your problem and the error is that you provide undefined as a value to input and do not assign any onChange event initially.

More specifically you pass this.state.empty as a value, which is assigned an undefined in the state and null as onChange handler.

When react gets an undefined as a value of input and no handler, it assumes that this input is uncontrolled and all the changes will be handled by the DOM and react itself should not do anything with it.

But, later down the road, based on user action, your input gets another value this.state.sharedInputMo and onChange handler that turns it into controlled input. This change causes the confusion of react and gives you this warning.

0
votes

So after giving it a night's rest I solved my own problem.
This is for everyone who needs to switch between a controlled and uncontrolled object and has the same error.

Use a Switch statement to switch between a controlled and uncontrolled object. This way, React will see them both as a separate and different object; thus in front end it will seem as if you have the same object.

switch(this.state['lock' + day + '_mo']) {
                case true:
                    return (
                        <div className='ohTableContentBlock'>
                            <input type='time' value={this.state.sharedInputMo} onChange={inputValue => this.setState({sharedInputMo2 : inputValue.target.value })} />
                        </div>
                    );
                case false:
                    return (
                        <div className='ohTableContentBlock'>
                            <input type='time' />
                        </div>
                    );
                default:
                    return (
                        <div className='ohTableContentBlock'>
                            <input type='time' />
                        </div>
                    );
            }