Aktualizacja stanu zmian rekwizytów w formularzu React

184

Mam problem z formularzem React i prawidłowym zarządzaniem stanem. Mam pole wprowadzania czasu w formie (modalnej). Wartość początkowa jest ustawiana jako zmienna stanu w getInitialStatei jest przekazywana z komponentu nadrzędnego. To samo w sobie działa dobrze.

Problem pojawia się, gdy chcę zaktualizować domyślną wartość start_time za pomocą komponentu nadrzędnego. Sama aktualizacja odbywa się w komponencie nadrzędnym za pośrednictwem setState start_time: new_time. Jednak w mojej formie domyślna wartość parametru czas_początkowy nigdy się nie zmienia, ponieważ jest definiowana tylko raz getInitialState.

Próbowałem użyć componentWillUpdatedo wymuszenia zmiany stanu setState start_time: next_props.start_time, która faktycznie działała, ale dała mi Uncaught RangeError: Maximum call stack size exceededbłędy.

Więc moje pytanie brzmi: jaki jest właściwy sposób aktualizacji stanu w tym przypadku? Czy myślę o tym jakoś źle?

Aktualny kod:

@ModalBody = React.createClass
  getInitialState: ->
    start_time: @props.start_time.format("HH:mm")

  #works but takes long and causes:
  #"Uncaught RangeError: Maximum call stack size exceeded"
  componentWillUpdate: (next_props, next_state) ->
    @setState(start_time: next_props.start_time.format("HH:mm"))

  fieldChanged: (fieldName, event) ->
    stateUpdate = {}
    stateUpdate[fieldName] = event.target.value
    @setState(stateUpdate)

  render: ->
    React.DOM.div
      className: "modal-body"
      React.DOM.form null,
        React.createElement FormLabelInputField,
          type: "time"
          id: "start_time"
          label_name: "Start Time"
          value: @state.start_time
          onChange: @fieldChanged.bind(null, "start_time”)

@FormLabelInputField = React.createClass
  render: ->
    React.DOM.div
      className: "form-group"
      React.DOM.label
        htmlFor: @props.id
        @props.label_name + ": "
      React.DOM.input
        className: "form-control"
        type: @props.type
        id: @props.id
        value: @props.value
        onChange: @props.onChange
David Basalla
źródło

Odpowiedzi:

287

componentWillReceiveProps jest nieefektywny, ponieważ reaguje 16: zamiast tego użyj getDerivedStateFromProps

Jeśli dobrze rozumiem, masz komponent nadrzędny, który jest przekazywany start_timedo ModalBodykomponentu, który przypisuje go do własnego stanu? I chcesz zaktualizować ten czas od elementu nadrzędnego, a nie elementu podrzędnego.

React ma kilka wskazówek dotyczących radzenia sobie z tym scenariuszem. (Uwaga: jest to stary artykuł, który został usunięty z Internetu. Oto link do bieżącego dokumentu na temat rekwizytów składników ).

Używanie rekwizytów do generowania stanu getInitialStateczęsto prowadzi do powielania „źródła prawdy”, czyli tam, gdzie są prawdziwe dane. Wynika to z faktu, że getInitialStatewywoływane jest tylko przy pierwszym tworzeniu komponentu.

O ile to możliwe, obliczaj wartości „w locie”, aby upewnić się, że nie zsynchronizują się później i nie spowodują problemów z konserwacją.

Zasadniczo, ilekroć przypisujesz rodzica propsdo dziecka, statemetoda renderowania nie zawsze jest wywoływana przy aktualizacji prop. Musisz wywołać go ręcznie, używając componentWillReceivePropsmetody.

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}
Brad B.
źródło
84
Przestarzałe od React 16
koleś
7
@ koleś To jeszcze nie jest przestarzałe, to, o czym mówisz, to tylko heads up na przyszłość. Cytuję[..]going to be deprecated in the future
paddotk
7
@poepje Może nie jest jeszcze przestarzałe, ale według obecnego standardu jest uważane za niebezpieczne i prawdopodobnie należy go unikać
ujawnia
12
Więc jaki powinien być nowy sposób na zrobienie tego po wycofaniu składnika componentWillReceiveProps?
Boris D. Teoharov,
5
@ Boris Teraz zespół reagujący w zasadzie mówi ci, żebyś się wypchał. Dają ci nową metodę o nazwie getDerivedStateFromProps. Problem polega na tym, że jest to metoda statyczna. Oznacza to, że nie można nic zrobić asynchronicznie, aby zaktualizować stan (ponieważ trzeba natychmiast zwrócić nowy stan), nie można również uzyskać dostępu do metod klas ani pól. Możesz także użyć memoizacji, ale to nie pasuje do każdego przypadku użycia. Po raz kolejny zespół reagujący chce zmusić swój sposób robienia rzeczy. To wyjątkowo głupia i obezwładniająca decyzja projektowa.
ig-dev,
76

Najwyraźniej rzeczy się zmieniają .... getDerivedStateFromProps () jest teraz preferowaną funkcją.

class Component extends React.Component {
  static getDerivedStateFromProps(props, current_state) {
    if (current_state.value !== props.value) {
      return {
        value: props.value,
        computed_prop: heavy_computation(props.value)
      }
    }
    return null
  }
}

(powyższy kod autorstwa danburzo @ github)

ErichBSchulz
źródło
7
Do Twojej wiadomości, musisz wrócić również, nulljeśli nic nie powinno się zmienić, więc zaraz po tym, jeśli, powinieneś iśćreturn null
Ilgıt Yıldırım
@ IlgıtYıldırım - edytowałem kod, odkąd 4 osoby wypowiedziały się za twoim komentarzem - czy to naprawdę robi różnicę?
ErichBSchulz
Istnieje całkiem niezły zasób, który getDerivedStateFromPropsdogłębnie omawia
odkrywa
2
getDerivedStateFromProps jest zmuszony do bycia statycznym. Oznacza to, że nie można nic zrobić asynchronicznie, aby zaktualizować stan, ani nie można uzyskać dostępu do metod klas ani pól. Po raz kolejny zespół reagujący chce zmusić swój sposób robienia rzeczy. To wyjątkowo głupia i obezwładniająca decyzja projektowa.
ig-dev,
39

componentWillReceiveProps jest przestarzałe, ponieważ używanie go „często prowadzi do błędów i niespójności”.

Jeśli coś zmienia się z zewnątrz, rozważ całkowite zresetowanie komponentu potomnegokey .

Dostarczenie keyrekwizytu do komponentu potomnego zapewnia, że ​​ilekroć wartość keyzmian z zewnątrz, ten komponent jest renderowany ponownie. Na przykład,

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Na temat jego wydajności:

Chociaż może to brzmieć wolno, różnica w wydajności jest zwykle nieznaczna. Użycie klucza może być nawet szybsze, jeśli komponenty mają ciężką logikę, która działa na aktualizacjach, ponieważ dla tego poddrzewa jest pomijane różnicowanie.

Lucia
źródło
1
Klucz, sekret! Działa doskonale w React 16, jak wspomniano powyżej
Darren Sweeney
klucz nie zadziała, jeśli jest to obiekt, a nie masz unikalnego ciągu
użytkownik3468806
Klucz działa dla obiektów, zrobiłem to. Oczywiście miałem jednak unikatowy ciąg klucza.
tsujp
@ user3468806 Jeśli nie jest to złożony obiekt z zewnętrznymi odniesieniami, możesz użyć go, JSON.stringify(myObject)aby uzyskać unikalny klucz ze swojego obiektu.
Roy Prins,
24

Dostępna jest również funkcja componentDidUpdate .

Sygnatura funkcji:

componentDidUpdate(prevProps, prevState, snapshot)

Wykorzystaj to jako okazję do pracy na DOM, gdy komponent zostanie zaktualizowany. Nie jest wywoływany na początku render.

Do zobaczenia prawdopodobnie nie potrzebujesz artykułu o stanie pochodnym , który opisuje Anti-Pattern zarówno dla, jak componentDidUpdatei dla getDerivedStateFromProps. Uważam to za bardzo przydatne.

arminfro
źródło
W końcu używam, componentDidUpdateponieważ jest prosty i bardziej odpowiedni dla większości przypadków.
KeitelDOG
14

Nowym sposobem przechwytywania tego jest użycie useEffect zamiast componentWillReceiveProps w stary sposób:

componentWillReceiveProps(nextProps) {
  // You don't have to do this check first, but it can help prevent an unneeded render
  if (nextProps.startTime !== this.state.startTime) {
    this.setState({ startTime: nextProps.startTime });
  }
}

staje się następujący w funkcjonalnym elemencie napędzanym hakami:

// store the startTime prop in local state
const [startTime, setStartTime] = useState(props.startTime)
// 
useEffect(() => {
  if (props.startTime !== startTime) {
    setStartTime(props.startTime);
  }
}, [props.startTime]);

ustawiamy stan za pomocą setState, za pomocą useEffect sprawdzamy zmiany w określonym rekwidziecie i podejmujemy akcję, aby zaktualizować stan po zmianie rekwizytu.

MMO
źródło
5

Prawdopodobnie nie potrzebujesz stanu pochodnego

1. Ustaw klucz od rodzica

Gdy klucz ulegnie zmianie, React utworzy nową instancję składnika, zamiast aktualizować bieżącą. Klucze są zwykle używane do list dynamicznych, ale są również przydatne tutaj.

2. Użyj getDerivedStateFromProps/componentWillReceiveProps

Jeśli z jakiegoś powodu klucz nie działa (być może inicjowanie komponentu jest bardzo drogie)

Za pomocą getDerivedStateFromPropsmożesz zresetować dowolną część stanu, ale w tej chwili wydaje się to trochę wadliwe (v16.7) !, patrz powyższy link do użycia

Ghominejad
źródło
2

Z dokumentacji reagowania: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Kasowanie stanu przy zmianie rekwizytów jest Anty Wzorem

Od React 16 składnik componentWillReceiveProps jest przestarzały. Z dokumentacji reagowania zalecane jest w tym przypadku podejście

  1. W pełni kontrolowany element: ParentComponentz ModalBodybędzie właścicielem start_timestanu. To nie jest moje preferowane podejście w tym przypadku, ponieważ uważam, że modal powinien posiadać ten stan.
  2. W pełni niekontrolowany komponent z kluczem: takie jest moje preferowane podejście. Przykład z dokumentacji reagowania: https://codesandbox.io/s/6v1znlxyxn . Będziesz w pełni właścicielem swojego start_timestanu ModalBodyi będziesz używać go getInitialStatetak, jak już to zrobiłeś. Aby zresetować start_timestan, wystarczy zmienić klucz zParentComponent
Lu Tran
źródło
0

Użyj Memoize

Wyprowadzenie stanu przez operację jest bezpośrednią manipulacją rekwizytami, bez potrzeby prawdziwego wyprowadzania. Innymi słowy, jeśli masz rekwizyt, który można wykorzystać lub przekształcić bezpośrednio, nie ma potrzeby przechowywania rekwizytu w stanie .

Biorąc pod uwagę, że wartością stanu start_timejest po prostu rekwizyt start_time.format("HH:mm"), informacje zawarte w rekwizytach są już same w sobie wystarczające do aktualizacji komponentu.

Jeśli jednak chcesz wywoływać format tylko w przypadku zmiany rekwizytów, poprawnym sposobem wykonania tego w najnowszej dokumentacji byłoby użycie funkcji Zapamiętaj: https://reactjs.org/blog/2018/06/07/you-probably-dont- need-pochodzą-stan.html # what-about-memoization

DannyMoshe
źródło
-1

Myślę, że użycie ref jest dla mnie bezpieczne, nie potrzebuję dbać o jakąś metodę powyżej.

class Company extends XComponent {
    constructor(props) {
        super(props);
        this.data = {};
    }
    fetchData(data) {
        this.resetState(data);
    }
    render() {
        return (
            <Input ref={c => this.data['name'] = c} type="text" className="form-control" />
        );
    }
}
class XComponent extends Component {
    resetState(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && typeof this.data[property] !== 'undefined') {
                if ( obj[property] !== this.data[property].state.value )
                    this.data[property].setState({value: obj[property]});
                else continue;
            }
            continue;
        }
    }
}
Maj Pogoda VN
źródło
Myślę, że ta odpowiedź jest tajemnicza (kod jest trudny do odczytania i bez wyjaśnienia / powiązania z problemem OP) i nie rozwiązuje problemu OP, czyli sposobu obsługi stanu początkowego.
netchkin