Phản ứng - thay đổi đầu vào không kiểm soát


360

Tôi có một thành phần phản ứng đơn giản với hình thức mà tôi tin là có một đầu vào được kiểm soát:

import React from 'react';

export default class MyForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
    }

    render() {
        return (
            <form className="add-support-staff-form">
                <input name="name" type="text" value={this.state.name} onChange={this.onFieldChange('name').bind(this)}/>
            </form>
        )
    }

    onFieldChange(fieldName) {
        return function (event) {
            this.setState({[fieldName]: event.target.value});
        }
    }
}

export default MyForm;

Khi tôi chạy ứng dụng của mình, tôi nhận được cảnh báo sau:

Cảnh báo: MyForm đang thay đổi kiểu nhập văn bản không kiểm soát được kiểm soát. Các yếu tố đầu vào không nên chuyển từ không kiểm soát sang kiểm soát (hoặc ngược lại). Quyết định giữa việc sử dụng phần tử đầu vào được kiểm soát hoặc không được kiểm soát trong suốt vòng đời của thành phần

Tôi tin rằng đầu vào của tôi được kiểm soát vì nó có giá trị. Tôi đang tự hỏi tôi đang làm gì sai?

Tôi đang sử dụng React 15.1.0

Câu trả lời:


510

Tôi tin rằng đầu vào của tôi được kiểm soát vì nó có giá trị.

Để một đầu vào được kiểm soát, giá trị của nó phải tương ứng với một biến trạng thái.

Điều kiện đó ban đầu không được đáp ứng trong ví dụ của bạn vì this.state.namekhông được đặt ban đầu. Do đó, đầu vào ban đầu không được kiểm soát. Khi onChangetrình xử lý được kích hoạt lần đầu tiên, this.state.nameđược đặt. Tại thời điểm đó, điều kiện trên được thỏa mãn và đầu vào được coi là được kiểm soát. Quá trình chuyển đổi từ không kiểm soát sang kiểm soát này tạo ra lỗi nhìn thấy ở trên.

Bằng cách khởi tạo this.state.nametrong hàm tạo :

ví dụ

this.state = { name: '' };

đầu vào sẽ được kiểm soát từ đầu, khắc phục sự cố. Xem các thành phần được điều khiển React để biết thêm ví dụ.

Không liên quan đến lỗi này, bạn chỉ nên có một lần xuất mặc định. Mã của bạn ở trên có hai.


7
Rất khó để đọc câu trả lời và làm theo ý tưởng nhiều lần nhưng câu trả lời này là cách kể chuyện hoàn hảo và khiến người xem hiểu cùng một lúc. Trả lời thần cấp!
surajnew55

2
Điều gì nếu bạn có các trường động trong một vòng lặp? ví dụ: bạn đặt tên trường là name={'question_groups.${questionItem.id}'}?
dùng3574492

2
Làm thế nào các lĩnh vực năng động làm việc. Đang tạo biểu mẫu một cách linh hoạt và sau đó đặt tên trường trong một đối tượng bên trong trạng thái, trước tiên tôi có phải đặt thủ công tên trường ở trạng thái sau đó sau đó chuyển chúng sang đối tượng của mình không?
Joseph

13
Câu trả lời này hơi sai. Một đầu vào được kiểm soát nếu valueprop có giá trị không null / không xác định. Prop không cần phải tương ứng với một biến trạng thái (nó có thể chỉ là một hằng số và thành phần vẫn sẽ được coi là được kiểm soát). Câu trả lời của Adam là chính xác hơn và nên được chấp nhận.
ecraig12345

2
Vâng - đây là câu trả lời sai về mặt kỹ thuật (nhưng hữu ích). Một lưu ý về điều này - nếu bạn đang xử lý các nút radio (có 'giá trị' được điều khiển hơi khác một chút), cảnh báo phản ứng này thực sự có thể ném ngay cả khi thành phần của bạn được điều khiển (hiện tại). github.com/facebook/react/issues/6779 và có thể được sửa bằng cách thêm một !! với sự thật của isChecked
mematvers

123

Khi bạn lần đầu tiên kết xuất thành phần của mình, this.state.namekhông được đặt, vì vậy nó sẽ ước tính undefinedhoặc null, và cuối cùng bạn sẽ chuyển value={undefined}hoặc value={null}đến input.

Khi ReactDOM kiểm tra xem một trường có được kiểm soát hay không, nó sẽ kiểm tra xemvalue != null (lưu ý rằng nó !=, không phải !==) và vì undefined == nulltrong JavaScript, nó quyết định rằng nó không được kiểm soát.

Vì vậy, khi onFieldChange()được gọi, this.state.nameđược đặt thành giá trị chuỗi, đầu vào của bạn chuyển từ không được kiểm soát sang bị kiểm soát.

Nếu bạn làm this.state = {name: ''}trong hàm tạo của mình, bởi vì '' != null, đầu vào của bạn sẽ có giá trị toàn bộ thời gian và thông báo đó sẽ biến mất.


5
Đối với những gì nó có giá trị, điều tương tự có thể xảy ra với this.props.<whatever>, đó là vấn đề của tôi. Cảm ơn, Adam!
Don

Vâng! Điều này cũng có thể xảy ra nếu bạn vượt qua một biến được tính toán mà bạn xác định render()hoặc một biểu thức trong chính thẻ đó là bất cứ thứ gì ước lượng undefined. Rất vui vì tôi có thể giúp!
Leigh Brenecki

1
Cảm ơn câu trả lời này: mặc dù nó không phải là câu trả lời được chấp nhận nhưng nó giải thích vấn đề tốt hơn nhiều so với chỉ "chỉ định một name".
máy

1
Tôi đã cập nhật câu trả lời của mình để giải thích cách hoạt động của đầu vào được kiểm soát. Điều đáng chú ý là điều quan trọng ở đây không phải là giá trị undefinedđang được thông qua ban đầu. Thay vào đó, thực tế là this.state.namekhông tồn tại như một biến trạng thái làm cho đầu vào không được kiểm soát. Ví dụ, có this.state = { name: undefined };kết quả là đầu vào được kiểm soát. Cần hiểu rằng điều quan trọng là giá trị đến từ đâu chứ không phải giá trị là gì.
fvss

1
@fvss this.state = { name: undefined }vẫn có thể dẫn đến đầu vào không được kiểm soát. <input value={this.state.name} />desugars để React.createElement('input', {value: this.state.name}). Vì việc truy cập vào một thuộc tính không tồn tại của một đối tượng trả về undefined, nên hàm này đánh giá chính xác cùng chức năng, gọi ra tên React.createElement('input', {value: undefined})Wap, khi đó namekhông được đặt hoặc được đặt rõ ràng undefined, do đó React hành xử theo cùng một cách. Bạn có thể thấy hành vi này trong JSFiddle này.
Leigh Brenecki

52

Một cách tiếp cận khác có thể là đặt giá trị mặc định bên trong đầu vào của bạn, như sau:

 <input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>

1
<input name = "name" type = "text" defaultValue = "" onChange = {this.onFieldChange ('name'). bind (this)} /> Tôi nghĩ rằng điều này cũng sẽ hoạt động
gandalf

Cảm ơn rất nhiều, đây chính xác là tôi đang tìm kiếm. Có bất kỳ nhược điểm hoặc vấn đề tiềm ẩn nào khi sử dụng mẫu này tôi nên ghi nhớ không?
Marcel Otten

Điều này làm việc với tôi, vì các trường được kết xuất động từ API nên tôi không biết tên của trường khi thành phần được gắn kết. Điều này đã làm việc một điều trị!
Jamie - Fenrir Digital Ltd

18

Tôi biết những người khác đã trả lời điều này rồi. Nhưng một yếu tố rất quan trọng ở đây có thể giúp những người khác gặp vấn đề tương tự:

Bạn phải có một onChangetrình xử lý được thêm vào trong trường nhập của bạn (ví dụ: textField, hộp kiểm, radio, v.v.). Luôn xử lý hoạt động thông qua onChangexử lý.

Thí dụ:

<input ... onChange={ this.myChangeHandler} ... />

Khi bạn đang làm việc với hộp kiểm, bạn có thể cần xử lý checkedtrạng thái của nó !!.

Thí dụ:

<input type="checkbox" checked={!!this.state.someValue} onChange={.....} >

Tham khảo: https://github.com/facebook/react/issues/6779#issuecomment-326314716


1
công trình này cho tôi, cảm ơn, vâng, tôi chắc chắn 100% phần trăm đồng ý với điều đó, trạng thái ban đầu là {}, vì vậy giá trị kiểm tra sẽ được undefined và làm cho nó kiểm soát được,
Ping Woo

13

Giải pháp đơn giản để giải quyết vấn đề này là đặt giá trị trống theo mặc định:

<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />

8

Một nhược điểm tiềm năng với việc đặt giá trị trường thành "" (chuỗi trống) trong hàm tạo là nếu trường là trường tùy chọn và không được chỉnh sửa. Trừ khi bạn thực hiện một số thao tác xoa bóp trước khi đăng biểu mẫu của mình, trường sẽ được lưu vào bộ lưu trữ dữ liệu của bạn dưới dạng một chuỗi trống thay vì NULL.

Sự thay thế này sẽ tránh các chuỗi trống:

constructor(props) {
    super(props);
    this.state = {
        name: null
    }
}

... 

<input name="name" type="text" value={this.state.name || ''}/>

6

Khi bạn sử dụng onChange={this.onFieldChange('name').bind(this)}trong đầu vào của mình, bạn phải khai báo chuỗi rỗng trạng thái của mình dưới dạng giá trị của trường thuộc tính.

cách không chính xác:

this.state ={
       fields: {},
       errors: {},
       disabled : false
    }

cách chính xác:

this.state ={
       fields: {
         name:'',
         email: '',
         message: ''
       },
       errors: {},
       disabled : false
    }

6

Trong trường hợp của tôi, tôi đã bỏ lỡ một cái gì đó thực sự tầm thường.

<input value={state.myObject.inputValue} />

Trạng thái của tôi là như sau khi tôi nhận được cảnh báo:

state = {
   myObject: undefined
}

Bằng cách xen kẽ trạng thái của tôi để tham chiếu đầu vào của giá trị của tôi, vấn đề của tôi đã được giải quyết:

state = {
   myObject: {
      inputValue: ''
   }
}

2
Cảm ơn bạn vì đã giúp tôi hiểu được những vấn đề thực sự tôi đã có
Rotimi-best

3

Nếu các đạo cụ trên thành phần của bạn được chuyển qua dưới dạng trạng thái, hãy đặt một giá trị mặc định cho các thẻ đầu vào của bạn

<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>


3

Một bản cập nhật cho điều này. Đối với móc phản ứng sử dụngconst [name, setName] = useState(" ")


Cảm ơn 4 bản cập nhật Jordan, lỗi này hơi khó giải quyết
Juan Salvador

Tại sao " "và không ""? Điều này khiến bạn mất bất kỳ văn bản gợi ý nào và nếu bạn nhấp và nhập, bạn sẽ có một khoảng trống lén lút trước bất kỳ dữ liệu nào bạn nhập.
Joshua Wade

1

Điều này thường chỉ xảy ra khi bạn không kiểm soát giá trị của tệp khi ứng dụng khởi động và sau một số sự kiện hoặc một số chức năng bị kích hoạt hoặc trạng thái thay đổi, bạn hiện đang cố gắng kiểm soát giá trị trong trường đầu vào.

Sự chuyển đổi không có quyền kiểm soát đầu vào và sau đó có quyền kiểm soát nó là nguyên nhân gây ra sự cố xảy ra ngay từ đầu.

Cách tốt nhất để tránh điều này là bằng cách khai báo một số giá trị cho đầu vào trong hàm tạo của thành phần. Vì vậy, phần tử đầu vào có giá trị từ khi bắt đầu ứng dụng.


0

Để tự động thiết lập các thuộc tính trạng thái cho đầu vào biểu mẫu và giữ cho chúng được kiểm soát, bạn có thể làm một cái gì đó như thế này:

const inputs = [
    { name: 'email', type: 'email', placeholder: "Enter your email"},
    { name: 'password', type: 'password', placeholder: "Enter your password"},
    { name: 'passwordConfirm', type: 'password', placeholder: "Confirm your password"},
]

class Form extends Component {
  constructor(props){
    super(props)
    this.state = {} // Notice no explicit state is set in the constructor
  }

  handleChange = (e) => {
    const { name, value } = e.target;

    this.setState({
      [name]: value
    }
  }

  handleSubmit = (e) => {
    // do something
  }

  render() {
     <form onSubmit={(e) => handleSubmit(e)}>
       { inputs.length ?
         inputs.map(input => {
           const { name, placeholder, type } = input;
           const value = this.state[name] || ''; // Does it exist? If so use it, if not use an empty string

           return <input key={name}  type={type} name={name} placeholder={placeholder} value={value} onChange={this.handleChange}/>
       }) :
         null
       }
       <button type="submit" onClick={(e) => e.preventDefault }>Submit</button>
     </form>    
  }
}

0

Chỉ cần tạo dự phòng thành '' nếu this.state.name là null.

<input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>

Điều này cũng hoạt động với các biến useState.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.