cphill
cphill

Reputation: 5914

Warning Controlled Component Changing to Uncontrolled

I have two hidden input fields that are being populated with the values from a prop when the node-fetch finishes and the values are being set without any issue, but I see the following warning.

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

While I understand the differences between controlled and uncontrolled, I can't seem to understand why my component would be creating a conflict between the two. Is there anything clear in my component code that could be causing this?

import React from 'react';
import isEqual from 'lodash/isEqual';

export default class DateFilter extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 
            startDateValue: '',
            endDateValue: ''
        };
    }

    componentDidMount() {
        this.setState({
            startDateValue: this.props.startDateQuery,
            endDateValue: this.props.endDateQuery
        });
    }

    handleChange(input, value) {
        this.setState({
            [input]: value
        })
    }

    componentWillReceiveProps(nextProps) {
        if (!isEqual(this.props, nextProps)){
            this.setState({ startDateValue: nextProps.startDateQuery, endDateValue: nextProps.endDateQuery });
        }
    }

    render() {
        return (
            <div className="col-md-3">
                <input type="hidden" name="_csrf" value={this.props.csrf} />
                <div className="input-group blog-filter-date-range-picker">
                    <p>Blog Date Range:</p>
                </div>
                <div className="input-group blogFilterDatePicker">
                    <span className="input-group-addon"><i className="glyphicon glyphicon-calendar"></i></span>
                    <input type="text" name="blogDateRange" className="form-control blogFilterDatePicker" autoComplete="off" />
                </div>
                <input type="hidden" name="blogStartDate" className="form-control" value={this.state.startDateValue} onChange={e => this.handleChange('startDateValue', e.target.value)} />
                <input type="hidden" name="blogEndDate" className="form-control" value={this.state.endDateValue} onChange={e => this.handleChange('endDateValue', e.target.value)} />
            </div>
        );
    }
}

Parent Component:

import React from 'react';

import CatgoryFilter from './SearchFormFilters/CategoryFilter';
import DepartmentFilter from './SearchFormFilters/DepartmentFilter';
import TeamFilter from './SearchFormFilters/TeamFilter';
import TypeFilter from './SearchFormFilters/TypeFilter';
import DateFilter from './SearchFormFilters/DateFilter';

//Activity Feed - Search Form
export default class ActivityFeedSearchForm extends React.Component {
    render() {
        var clearFilters;
        if(this.typeQuery || this.props.categoryQuery || this.props.departmentQuery || this.props.teamQuery || this.props.startDateQuery || this.props.endDateQuery || this.props.typeQuery){
            clearFilters = <a href="/app" id="clear-filter">Clear</a>;
        }

        return (
            <div className="row">
                <div className="annotation-search-form col-md-10 col-md-offset-1">
                    <div clas="row">
                        <form action="/app" method="post" className="annotation-filter-fields">
                            <DateFilter csrf={this.props.csrf} startDateQuery={this.props.startDateQuery} endDateQuery={this.props.endDateQuery} />
                            <TypeFilter typeQuery={this.props.typeQuery} />
                            <CatgoryFilter category={this.props.category} categoryQuery={this.props.categoryQuery} />
                            <DepartmentFilter department={this.props.department} departmentQuery={this.props.departmentQuery} />
                            <TeamFilter team={this.props.team} teamQuery={this.props.teamQuery} />
                            <div className="col-md-1 annotation-filter-section filter-button-container">
                                <button type="submit" id="annotation-filter-submit">Filter</button>
                                {clearFilters}
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        )
    }
}

Top-level Parent Component:

import React from 'react';
import fetch from 'node-fetch';
import ReactMarkdown from 'react-markdown';
import path from 'path';
import ActivityFeedSearchForm from './ActivityFeedSearchForm/ActivityFeedSearchForm';
import { API_ROOT } from '../config/api-config';

//GET /api/test and set to state
export default class ActivityFeed extends React.Component{
    constructor(props, context) {
        super(props, context);
        this.state = this.context.data || window.__INITIAL_STATE__ || { team: [] };
    }

    fetchList() {
            fetch(`${API_ROOT}` + '/api' + window.location.search, { compress: false })
                .then(res => {
                    return res.json();
                })  
                .then(data => {
                    this.setState({ 
                        startDateQuery: data.startDateQuery,
                        endDateQuery: data.endDateQuery,
                    });
                }) 
                .catch(err => {
                    console.log(err);
                });
        }

    componentDidMount() {
        this.fetchList();
    }

    render() {  
            return (
                <div>
                    <Navigation notifications={this.state.notifications}/>
                    <ActivityFeedSearchForm csrf={this.state.csrf} category={this.state.category} categoryQuery={this.state.categoryQuery} department={this.state.department} departmentQuery={this.state.departmentQuery} team={this.state.team} teamQuery={this.state.teamQuery} typeQuery={this.state.typeQuery} startDateQuery={this.state.startDateQuery} endDateQuery={this.state.endDateQuery} />
                    <div className="activity-feed-container">
                        <div className="container">
                            <OnboardingInformation onboarding={this.state.onboardingWelcome} />
                            <LoadingIndicator loading={this.state.isLoading} />
                            <ActivityFeedLayout {...this.state} />
                        </div>
                    </div>
                </div>
            )
    }
};

Upvotes: 1

Views: 1051

Answers (1)

Hemadri Dasari
Hemadri Dasari

Reputation: 33994

As you are dealing with only startDateValue and endDateValue in componentWillReceiveProps it’s good to compare them individually instead of whole props. Deep equality check is not happening for whole props and that’s why you get the warning

Change

    componentWillReceiveProps(nextProps) {
    if (!isEqual(this.props, nextProps)){
        this.setState({ startDateValue: nextProps.startDateQuery, endDateValue: nextProps.endDateQuery });
    }
}

To

    componentWillReceiveProps(nextProps) {
    if (this.props.startDateQuery != nextProps.startDateQuery && this.props.endDateQuery != nextProps.endDateQuery){
        this.setState({ startDateValue: nextProps.startDateQuery, endDateValue: nextProps.endDateQuery });
    }
}

Also you don’t need componentDidMount so remove that part in your code and update constructor code with below one

     constructor(props) {
         super(props);
         this.state = { 
              startDateValue: this.props.startDateQuery ? this.props.startDateQuery: '',
               endDateValue:this.props.endDateQuery ? this.props.endDateQuery:  ''
         };
        }
    }

And

Change

     <input type="hidden" name="_csrf" value={this.props.csrf} />

To

      <input type="hidden" name="_csrf" value={this.props.csrf ? this.props.csrf : "" />

And you are not binding handleChange so either bind it manually in constructor or change it to arrow function like below

Change

    handleChange(input, value) {
    this.setState({
        [input]: value
    })
}

To

    handleChange = (input, value) => {
    this.setState({
        [input]: value
    })
}

Upvotes: 2

Related Questions