Atualização do estado nas alterações de adereços no formulário React

184

Estou tendo problemas com um formulário React e gerenciando o estado corretamente. Eu tenho um campo de entrada de tempo em um formulário (em um modal). O valor inicial é definido como uma variável de estado getInitialStatee é passado de um componente pai. Isso por si só funciona bem.

O problema ocorre quando eu quero atualizar o valor padrão start_time através do componente pai. A atualização em si acontece no componente pai até setState start_time: new_time. No entanto, no meu formulário, o valor padrão start_time nunca muda, pois é definido apenas uma vez getInitialState.

Tentei usar componentWillUpdatepara forçar uma mudança de estado setState start_time: next_props.start_time, o que realmente funcionou, mas me deu Uncaught RangeError: Maximum call stack size exceedederros.

Então, minha pergunta é: qual é a maneira correta de atualizar o estado nesse caso? Estou pensando nisso errado de alguma forma?

Código atual:

@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
fonte

Respostas:

287

componentWillReceiveProps é depcricated desde reagir 16: uso getDerivedStateFromProps vez

Se bem entendi, você tem um componente pai que está passando start_timepara o ModalBodycomponente que o atribui ao seu próprio estado? E você deseja atualizar esse horário do pai, não de um componente filho.

O React tem algumas dicas para lidar com esse cenário. (Observe, este é um artigo antigo que foi removido da Web. Aqui está um link para o documento atual sobre adereços de componentes ).

O uso de adereços para gerar estado getInitialStategeralmente leva à duplicação da "fonte da verdade", ou seja, onde estão os dados reais. Isso ocorre porque getInitialStateé invocado apenas quando o componente é criado pela primeira vez.

Sempre que possível, calcule os valores imediatamente para garantir que eles não fiquem fora de sincronia mais tarde e causem problemas de manutenção.

Basicamente, sempre que você atribui os pais propsaos filhos, stateo método de renderização nem sempre é chamado na atualização de objetos. Você deve invocá-lo manualmente, usando o componentWillReceivePropsmétodo

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
fonte
84
Preterido a partir de React 16
cara
7
@dude Ainda não foi preterido, você se refere apenas a referências futuras. Eu cito[..]going to be deprecated in the future
paddotk 29/08/18
7
@poepje Pode não ser depreciado ainda, mas ele é visto como inseguro pela norma atual e provavelmente deve ser evitado
unflores
12
Então, qual deve ser a nova maneira de fazer isso depois que o componentWillReceiveProps foi preterido?
Boris D. Teoharov 20/04/19
5
@ Boris Agora, a equipe de reação está basicamente dizendo para você ficar empalhado. Eles fornecem um novo método, chamado getDerivedStateFromProps. O problema é que este é um método estático. Significando que você não pode fazer nada assíncrono para atualizar o estado (porque você precisa retornar o novo estado imediatamente), nem pode acessar métodos ou campos de classe. Você também pode usar a memorização, mas isso não se aplica a todos os casos de uso. Mais uma vez, a equipe de reação quer forçar sua maneira de fazer as coisas. É uma decisão de design extremamente estúpida e incapacitante.
ig-dev
76

Aparentemente, as coisas estão mudando ... getDerivedStateFromProps () agora é a função preferida.

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
  }
}

(código acima por danburzo @ github)

ErichBSchulz
fonte
7
FYI, você precisa voltar também nullse nada deve mudar tão logo após o seu caso, você deve ir comreturn null
Ilgıt Yıldırım
@ IlgıtYıldırım - editou o código desde que 4 pessoas votaram positivamente no seu comentário - isso realmente faz a diferença?
ErichBSchulz
Existe um recurso muito bom que se aprofunda em diferentes opções e por que você usaria uma getDerivedStateFromPropsou mais memoization reactjs.org/blog/2018/06/07/…
unflores
2
getDerivedStateFromProps é forçado a ser estático. Isso significa que você não pode fazer nada assíncrono para atualizar o estado, nem acessar métodos ou campos de classe. Mais uma vez, a equipe de reação quer forçar sua maneira de fazer as coisas. É uma decisão de design extremamente estúpida e incapacitante.
ig-dev
39

componentWillReceiveProps está sendo preterido porque usá-lo "geralmente leva a erros e inconsistências".

Se algo mudar de fora, considere redefinir completamente o componente filhokey .

Fornecer um keysuporte ao componente filho garante que, sempre que o valor das keyalterações for alterado externamente, esse componente seja renderizado novamente. Por exemplo,

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

Em seu desempenho:

Embora isso possa parecer lento, a diferença de desempenho geralmente é insignificante. O uso de uma chave pode ser ainda mais rápido se os componentes tiverem uma lógica pesada que é executada nas atualizações, pois a difusão é ignorada para essa subárvore.

Lucia
fonte
1
A chave, o segredo! Funciona perfeitamente no React 16, como mencionado acima
Darren Sweeney
chave não vai funcionar, se é um objeto e seu não tem uma cadeia exclusiva
user3468806
Key funciona para objetos, eu fiz. Claro que eu tinha uma string única para a chave.
tsujp 22/08/19
@ user3468806 Se não for um objeto complexo com referências externas, você poderá JSON.stringify(myObject)derivar uma chave exclusiva do seu objeto.
Roy Prins
24

Há também componentDidUpdate disponível.

Assinatura da função:

componentDidUpdate(prevProps, prevState, snapshot)

Use isso como uma oportunidade para operar no DOM quando o componente tiver sido atualizado. Não é chamado na inicial render.

Consulte Você provavelmente não precisa do artigo sobre o estado derivado , que descreve o antipadrão para ambos componentDidUpdatee getDerivedStateFromProps. Eu achei muito útil.

arminfro
fonte
Acabo usando componentDidUpdateporque é simples e é mais adequado para a maioria dos casos.
KeitelDOG 16/09/19
14

A nova maneira de fazer ganchos é usar useEffect em vez de componentWillReceiveProps da maneira antiga:

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 });
  }
}

torna-se o seguinte em um componente acionado por ganchos funcionais:

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

definimos o estado usando setState, usando useEffect, verificamos mudanças no suporte especificado e executamos a ação para atualizar o estado na alteração do suporte.

MMO
fonte
5

Você provavelmente não precisa de um estado derivado

1. Defina uma chave do pai

Quando uma chave é alterada, o React cria uma nova instância de componente em vez de atualizar a atual. As chaves geralmente são usadas para listas dinâmicas, mas também são úteis aqui.

2. Use getDerivedStateFromProps/componentWillReceiveProps

Se a chave não funcionar por algum motivo (talvez o componente seja muito caro para inicializar)

Ao usar, getDerivedStateFromPropsvocê pode redefinir qualquer parte do estado, mas parece um pouco problemático no momento (v16.7) !, consulte o link acima para saber o uso

Ghominejad
fonte
2

Na documentação do react : https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

Apagar o estado quando a alteração de objetos é um Antipadrão

Desde a React 16, componentWillReceiveProps está obsoleto. Na documentação de reação, a abordagem recomendada neste caso é usar

  1. Componente totalmente controlado: o ParentComponentda ModalBodyvontade possui o start_timeestado. Esta não é minha abordagem preferida neste caso, pois acho que o modal deve possuir esse estado.
  2. Componente totalmente não controlado com uma chave: esta é a minha abordagem preferida. Um exemplo da documentação do react: https://codesandbox.io/s/6v1znlxyxn . Você seria o dono start_timedo seu estado ModalBodye o usaria getInitialStateexatamente como já fez. Para redefinir o start_timeestado, basta alterar a chave doParentComponent
Lu Tran
fonte
0

Use Memoize

A derivação de estado do op é uma manipulação direta de adereços, sem a verdadeira derivação necessária. Em outras palavras, se você tem um suporte que pode ser utilizado ou transformado diretamente, não há necessidade de armazenar o suporte no estado .

Dado que o valor do estado de start_timeé simplesmente o suporte start_time.format("HH:mm"), as informações contidas no suporte já são suficientes para atualizar o componente.

No entanto, se você quiser chamar apenas o formato em uma alteração de suporte, a maneira correta de fazer isso de acordo com a documentação mais recente seria através do Memoize: https://reactjs.org/blog/2018/06/07/you-probably-dont- need-derivated state.html # what-about-memoization

DannyMoshe
fonte
-1

Eu acho que usar ref é seguro para mim, não precisa se preocupar com algum método acima.

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;
        }
    }
}
Tempo maio VN
fonte
Eu acho que essa resposta é enigmática (o código é dificilmente legível e sem qualquer explicação / vinculação ao problema do OP) e não aborda o problema do OP, que é como lidar com o estado inicial.
Netchkin #