React - zmiana niekontrolowanego wejścia

359

Mam prosty element reagujący z formularzem, który moim zdaniem ma jedno kontrolowane wejście:

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;

Po uruchomieniu aplikacji pojawia się następujące ostrzeżenie:

Ostrzeżenie: MyForm zmienia niekontrolowane wprowadzanie tekstu kontrolowanego. Elementy wejściowe nie powinny przełączać się z niekontrolowanego na kontrolowane (lub odwrotnie). Wybierz między użyciem kontrolowanego lub niekontrolowanego elementu wejściowego przez cały okres użytkowania komponentu

Uważam, że mój wkład jest kontrolowany, ponieważ ma wartość. Zastanawiam się, co robię źle?

Używam React 15.1.0

alexs333
źródło

Odpowiedzi:

509

Uważam, że mój wkład jest kontrolowany, ponieważ ma wartość.

Aby wejście podlegało kontroli, jego wartość musi odpowiadać wartości zmiennej stanu.

Ten warunek nie jest początkowo spełniony w twoim przykładzie, ponieważ this.state.namenie jest początkowo ustawiony. Dlatego dane wejściowe są początkowo niekontrolowane. Gdy program onChangeobsługi zostanie uruchomiony po raz pierwszy, this.state.namezostanie ustawiony. W tym momencie powyższy warunek jest spełniony, a wejście uważa się za kontrolowane. To przejście z niekontrolowanego do kontrolowanego powoduje błąd przedstawiony powyżej.

Inicjując this.state.namew konstruktorze:

na przykład

this.state = { name: '' };

dane wejściowe będą kontrolowane od samego początku, co rozwiąże problem. Zobacz React Controlled Components, aby uzyskać więcej przykładów.

Nie związany z tym błędem, powinieneś mieć tylko jeden domyślny eksport. Twój kod powyżej ma dwa.

fvgs
źródło
7
Trudno jest przeczytać odpowiedź i podążać za nią wiele razy, ale ta odpowiedź jest doskonałym sposobem na opowiadanie historii i jednoczesne zrozumienie przez widza. Bóg poziomu odpowiedzi!
surajnew55
2
Co jeśli masz dynamiczne pola w pętli? np. ustawiłeś nazwę pola na name={'question_groups.${questionItem.id}'}?
user3574492,
2
Jak działałyby pola dynamiczne. Czy dynamicznie generuję formularz, a następnie ustawiam nazwy pól w stanie wewnątrz obiektu, czy nadal muszę najpierw ręcznie ustawić nazwy pól w stanie, a następnie przenieść je do mojego obiektu?
Joseph
13
Ta odpowiedź jest nieco niepoprawna. Wejście jest kontrolowane, jeśli valuerekwizyt ma wartość inną niż zero / niezdefiniowana. Podpora nie musi odpowiadać zmiennej stanu (może to być po prostu stała, a komponent nadal będzie uważany za sterowany). Odpowiedź Adama jest bardziej poprawna i powinna zostać zaakceptowana.
ecraig12345
2
Tak - technicznie jest to zła odpowiedź (ale pomocna). Uwaga na ten temat - jeśli masz do czynienia z przyciskami radiowymi (których „wartość” jest kontrolowana nieco inaczej), to ostrzeżenie o reakcji może faktycznie rzucić, nawet jeśli twój komponent jest kontrolowany (obecnie). github.com/facebook/react/issues/6779 , i można to naprawić, dodając !! do prawdziwości isChecked
mheavers
122

Kiedy renderujesz swój komponent po raz pierwszy, this.state.namenie jest on ustawiony, więc ocenia on na undefinedlub null, i kończysz przekazywanie value={undefined}lub value={null}na twój input.

Kiedy ReactDOM sprawdza, czy pole jest kontrolowane, sprawdza, czyvalue != null (pamiętaj, że to !=nie jest !==), a ponieważ undefined == nullw JavaScript decyduje, że jest niekontrolowane.

Kiedy onFieldChange()jest wywoływany, this.state.namejest ustawiany na wartość ciągu, dane wejściowe przechodzą od niekontrolowanego do kontrolowanego.

Jeśli zrobisz to this.state = {name: ''}w swoim konstruktorze, ponieważ '' != nulltwoje dane wejściowe będą miały wartość przez cały czas, a ten komunikat zniknie.

Leigh Brenecki
źródło
5
Jeśli chodzi o to, co jest warte, to samo może się zdarzyć this.props.<whatever>, co było moim problemem. Dzięki, Adam!
Don
Tak! Może się to również zdarzyć, jeśli przekażesz zmienną obliczeniową, którą zdefiniujesz render(), lub wyrażenie w samym znaczniku - cokolwiek, co zostanie ocenione undefined. Cieszę się, że mogłem pomóc!
Leigh Brenecki,
1
Dzięki za odpowiedź: chociaż nie jest to zaakceptowana, wyjaśnia problem znacznie lepiej niż tylko „podaj name”.
machineghost
1
Zaktualizowałem swoją odpowiedź, aby wyjaśnić, jak działa kontrolowane wejście. Warto zauważyć, że to, co się tutaj liczy, nie polega na tym, undefinedże początkowo przekazywana jest wartość . Raczej fakt, że this.state.namenie istnieje jako zmienna stanu, powoduje, że dane wejściowe są niekontrolowane. Na przykład posiadanie this.state = { name: undefined };spowoduje, że wejście będzie kontrolowane. Należy rozumieć, że ważne jest, skąd pochodzi wartość, a nie jaka jest wartość.
fvgs
1
@ fvgs this.state = { name: undefined }nadal spowoduje niekontrolowane wprowadzanie danych. <input value={this.state.name} />desugars to React.createElement('input', {value: this.state.name}). Ponieważ uzyskuje się dostęp do nieistniejącej właściwości obiektu, zwraca undefinedto dokładnie to samo wywołanie funkcji - React.createElement('input', {value: undefined})czy namenie jest ustawione ani jawnie ustawione na undefined, więc React zachowuje się w ten sam sposób. Możesz zobaczyć to zachowanie w tym JSFiddle.
Leigh Brenecki
52

Innym podejściem może być ustawienie wartości domyślnej w danych wejściowych, na przykład:

 <input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>
JpCrow
źródło
1
<input name = "name" type = "text" defaultValue = "" onChange = {this.onFieldChange ('name'). bind (this)} /> Myślę, że to też zadziała
gandalf
Wielkie dzięki, właśnie tego szukałem. Czy są jakieś wady lub potencjalne problemy z korzystaniem z tego wzorca, o którym powinienem pamiętać?
Marcel Otten,
To działało dla mnie, ponieważ pola są dynamicznie renderowane z interfejsu API, więc nie znam nazwy pola, gdy komponent jest montowany. To zadziałało!
Jamie - Fenrir Digital Ltd
18

Wiem, że inni już na to odpowiedzieli. Ale bardzo ważny czynnik, który może pomóc innym osobom, które mają podobny problem:

Musisz mieć onChangemoduł obsługi dodany w polu wejściowym (np. Pole tekstowe, pole wyboru, radio itp.). Zawsze obsługuj aktywność przez onChangemoduł obsługi.

Przykład:

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

Podczas pracy z polem wyboru może być konieczne obsługiwanie jego checkedstanu za pomocą !!.

Przykład:

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

Odniesienie: https://github.com/facebook/react/issues/6779#issuecomment-326314716

Muhammad Hannan
źródło
1
to działa dla mnie, dziękuję, tak, jestem w 100% zgodny z tym, stan początkowy to {}, więc zaznaczona wartość będzie niezdefiniowana i sprawi, że będzie niekontrolowana,
Ping Woo
13

Prostym rozwiązaniem tego problemu jest ustawienie domyślnie pustej wartości:

<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />
MUSTAPHA GHLISSI
źródło
8

Jednym potencjalnym minusem przy ustawianiu wartości pola na „” (pusty ciąg) w konstruktorze jest to, że pole jest polem opcjonalnym i nie jest edytowane. O ile nie wykonasz masowania przed opublikowaniem formularza, pole zostanie utrwalone do przechowywania danych jako pusty ciąg zamiast NULL.

Ta alternatywa pozwoli uniknąć pustych ciągów:

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

... 

<input name="name" type="text" value={this.state.name || ''}/>
Greg R. Taylor
źródło
6

Kiedy używasz onChange={this.onFieldChange('name').bind(this)}w swoich danych wejściowych, musisz zadeklarować swój pusty ciąg znaków jako wartość pola właściwości.

niewłaściwy sposób:

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

właściwa droga:

this.state ={
       fields: {
         name:'',
         email: '',
         message: ''
       },
       errors: {},
       disabled : false
    }
KARTHIKEYAN.A
źródło
6

W moim przypadku brakowało mi czegoś naprawdę trywialnego.

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

Mój stan był następujący, gdy otrzymywałem ostrzeżenie:

state = {
   myObject: undefined
}

Poprzez naprzemienne zmienianie mojego stanu w celu odniesienia danych wejściowych mojej wartości , mój problem został rozwiązany:

state = {
   myObject: {
      inputValue: ''
   }
}
Menelaos Kotsollaris
źródło
2
Dziękuję za pomoc w zrozumieniu prawdziwego problemu, który miałem
rotimi-best
3

Jeśli rekwizyty w komponencie zostały przekazane jako stan, ustaw wartość domyślną dla tagów wejściowych

<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>
Cesarz Krauser
źródło
3

Ustaw wartość właściwości „name” w stanie początkowym.

this.state={ name:''};

lakmal_sathyajith
źródło
3

Aktualizacja do tego. Do użycia haków Reactconst [name, setName] = useState(" ")

Jordan Mullen
źródło
Dzięki 4 aktualizacji Jordan ten błąd jest nieco trudny do rozwiązania
Juan Salvador
Dlaczego " "nie ""? Powoduje to utratę tekstu podpowiedzi, a po kliknięciu i wpisaniu pojawia się sprytna spacja przed wprowadzeniem danych.
Joshua Wade
1

Zasadniczo dzieje się tak tylko wtedy, gdy nie kontrolujesz wartości pola przy uruchomieniu aplikacji i po uruchomieniu jakiegoś zdarzenia lub funkcji lub zmianie stanu, próbujesz teraz kontrolować wartość w polu wejściowym.

To przejście z braku kontroli nad danymi wejściowymi, a następnie nad nimi kontroli, powoduje, że problem występuje przede wszystkim.

Najlepszym sposobem na uniknięcie tego jest zadeklarowanie wartości dla danych wejściowych w konstruktorze komponentu. Tak, aby element wejściowy miał wartość od początku aplikacji.

Ashish Singh
źródło
0

Aby dynamicznie ustawiać właściwości stanu dla danych wejściowych formularza i kontrolować je, możesz zrobić coś takiego:

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>    
  }
}
znak
źródło
0

Po prostu utwórz rezerwę na „”, jeśli this.state.name ma wartość NULL.

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

Działa to również ze zmiennymi useState.

RobKohr
źródło