Como atualizar o estado dos pais no React?

350

Minha estrutura é a seguinte:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

O componente 3 deve exibir alguns dados, dependendo do estado do componente 5. Como os objetos são imutáveis, não posso simplesmente salvar seu estado no componente 1 e encaminhá-lo, certo? E sim, eu li sobre redux, mas não quero usá-lo. Espero que seja possível resolvê-lo apenas com reagir. Estou errado?

wklm
fonte
20
super fácil: passe a propriedade parent-setState-Function via para o componente filho: <MyChildComponent setState = {p => {this.setState (p)}} /> No componente filho, chame-o por this.props. setState ({myObj, ...});
Marcel Ennix
@MarcelEnnix, seu comentário economiza meu tempo. Obrigado.
Dinith Minura
<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />se você vai usar esse hack, certifique-se de apoiar o retorno de chamada.
Barkermn01
4
Passar um retorno de chamada para definir o estado do pai é uma prática muito ruim que pode levar a problemas de manutenção. Ele quebra o encapsulamento e torna os componentes 2 4 e 5 firmemente acoplados a 1. Se percorrer esse caminho, não será possível reutilizar nenhum desses componentes filhos em outro lugar. É melhor que você tenha adereços específicos para que os componentes filhos possam acionar eventos sempre que algo acontecer; o componente pai manipulará esse evento corretamente.
Pato Loco
@MarcelEnnix, por que os colchetes this.setState(p)? Eu tentei sem eles e parece funcionar (eu sou muito novo em reagir)
Biganon

Respostas:

678

Para comunicação entre pais e filhos, você deve passar uma função definindo o estado de pai para filho, como este

class Parent extends React.Component {
  constructor(props) {
    super(props)

    this.handler = this.handler.bind(this)
  }

  handler() {
    this.setState({
      someVar: 'some value'
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {this.props.handler}/ >
  }
}

Dessa forma, o filho pode atualizar o estado dos pais com a chamada de uma função passada com adereços.

Mas você terá que repensar a estrutura de seus componentes, porque, como eu entendo, os componentes 5 e 3 não estão relacionados.

Uma solução possível é envolvê-los em um componente de nível superior que conterá o estado dos componentes 1 e 3. Esse componente definirá o estado de nível inferior por meio de adereços.

Ivan
fonte
6
por que você precisa this.handler = this.handler.bind (this) e não apenas a função de manipulador que define o estado?
chemook78
35
@ chemook78 nos métodos das classes ES6 React não se ligam automaticamente à classe. Portanto, se não adicionarmos o this.handler = this.handler.bind(this)construtor, thisdentro da handlerfunção fará referência ao fechamento da função, não à classe. Se não quiser vincular todas as suas funções no construtor, há mais duas maneiras de lidar com isso usando as funções de seta. Você pode simplesmente escrever o manipulador de cliques como onClick={()=> this.setState(...)}ou usar inicializadores de propriedades juntamente com funções de seta, conforme descrito aqui babeljs.io/blog/2015/06/07/react-on-es6-plus em "Funções de seta"
Ivan
11
Aqui está um exemplo disso em ação: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb
5
Tudo isso faz sentido, por que e.preventDefault? e isso requer jquery?
21817 Vincent Velloello
11
Pergunta rápida, isso não permite a atribuição de um estado local dentro da criança?
ThisGuyCant Até
50

Encontrei a seguinte solução de trabalho para transmitir o argumento da função onClick de child para o componente pai:

Versão com passagem de um método ()

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div><button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component {

    constructor(props) {
        super(props);
        var handleToUpdate  = this.handleToUpdate.bind(this);
        var arg1 = '';
    }

    handleToUpdate(someArg){
            alert('We pass argument from Child to Parent: ' + someArg);
            this.setState({arg1:someArg});
    }

    render() {
        var handleToUpdate  =   this.handleToUpdate;

        return (<div>
                    <ChildB handleToUpdate = {handleToUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

Veja JSFIDDLE

Versão com passagem da função Arrow

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div>
          <button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component { 
    constructor(props) {
        super(props);
    }

    handleToUpdate = (someArg) => {
            alert('We pass argument from Child to Parent: ' + someArg);
    }

    render() {
        return (<div>
            <ChildB handleToUpdate = {this.handleToUpdate} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

Veja JSFIDDLE

romano
fonte
Este é bom! você poderia explicar esta peça: <ChildB handleToUpdate = {handleToUpdate.bind(this)} />por que teve que ligar novamente?
Dane
@ Dane - você precisa vincular o contexto para que ele seja o pai, para que, quando thisfor chamado dentro do filho, thisse refira ao estado do pai e não ao filho. Este é o melhor fechamento!
Casey
@ Casey, mas não estamos fazendo isso no construtor? E isso não é suficiente?
Dane
Você está totalmente certo! Eu senti falta disso. Sim, se você já fez isso no construtor, está pronto!
Casey
Você é um companheiro de lenda! Isso vai manter os componentes bem auto-contido, sem ter que ser forçado a criar componentes pai para lidar com as trocas de estado
adamj
13

Quero agradecer a resposta mais votada por me dar a idéia do meu próprio problema, basicamente a variação dele com a função de seta e a passagem de param do componente filho:

 class Parent extends React.Component {
  constructor(props) {
    super(props)
    // without bind, replaced by arrow func below
  }

  handler = (val) => {
    this.setState({
      someVar: val
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {() => this.props.handler('the passing value')}/ >
  }
}

Espero que ajude alguém.

Ardhi
fonte
o que há de especial na função de seta na chamada direta?
Ashish Kamble
@AshishKamble as thisfunções da seta in se referem ao contexto do pai (ou seja, Parentclasse).
CPHPython
Esta é uma resposta duplicada. Você pode adicionar um comentário à resposta aceita e mencionar esse recurso experimental para usar a função de seta na aula.
Arashsoft 28/08/19
10

Eu gosto da resposta sobre a passagem de funções, é uma técnica muito útil.

Por outro lado, você também pode conseguir isso usando pub / sub ou usando uma variante, um despachante, como o Flux . A teoria é super simples, o componente 5 envia uma mensagem que o componente 3 está ouvindo. O componente 3 atualiza seu estado que aciona a nova renderização. Isso requer componentes com estado, que, dependendo do seu ponto de vista, podem ou não ser um antipadrão. Sou contra eles pessoalmente e prefiro que outra coisa esteja ouvindo despachos e alterações de estado de cima para baixo (o Redux faz isso, mas adiciona terminologia adicional).

import { Dispatcher } from flux
import { Component } from React

const dispatcher = new Dispatcher()

// Component 3
// Some methods, such as constructor, omitted for brevity
class StatefulParent extends Component {
  state = {
    text: 'foo'
  } 

  componentDidMount() {
    dispatcher.register( dispatch => {
      if ( dispatch.type === 'change' ) {
        this.setState({ text: 'bar' })
      }
    }
  }

  render() {
    return <h1>{ this.state.text }</h1>
  }
}

// Click handler
const onClick = event => {
  dispatcher.dispatch({
    type: 'change'
  })
}

// Component 5 in your example
const StatelessChild = props => {
  return <button onClick={ onClick }>Click me</button> 
}

O pacote do despachante com o Flux é muito simples, ele simplesmente registra retornos de chamada e os invoca quando ocorre qualquer despacho, passando pelo conteúdo do despacho (no exemplo resumido acima não há payloadcom o despacho, apenas um ID da mensagem). Você pode adaptar isso ao pub / sub tradicional (por exemplo, usando o EventEmitter de eventos ou alguma outra versão) com muita facilidade, se isso fizer mais sentido para você.

Estilos Matt
fonte
Os componentes do My Reacts estão "funcionando" no navegador, como em um tutorial oficial ( facebook.github.io/react/docs/tutorial.html ) Tentei incluir o Flux no browserify, mas o navegador diz que o Dispatcher não foi encontrado :(
wklm
2
A sintaxe que usei foi ES2016 módulo de sintaxe que requer transpilation (eu uso Babel , mas existem outros, o babelify transformar pode ser usado com browserify), ele transpiles paravar Dispatcher = require( 'flux' ).Dispatcher
Styles Matt
8

Encontrei a seguinte solução de trabalho para transmitir o argumento da função onClick de child para o componente pai com param:

classe pai:

class Parent extends React.Component {
constructor(props) {
    super(props)

    // Bind the this context to the handler function
    this.handler = this.handler.bind(this);

    // Set some state
    this.state = {
        messageShown: false
    };
}

// This method will be sent to the child component
handler(param1) {
console.log(param1);
    this.setState({
        messageShown: true
    });
}

// Render the child component and set the action property with the handler as value
render() {
    return <Child action={this.handler} />
}}

classe criança:

class Child extends React.Component {
render() {
    return (
        <div>
            {/* The button will execute the handler function set by the parent component */}
            <Button onClick={this.props.action.bind(this,param1)} />
        </div>
    )
} }
H. parnian
fonte
2
Alguém pode dizer se essa é uma solução aceitável (particularmente interessado em passar o parâmetro conforme sugerido).
ilans
param1é apenas exibir no console não conseguir atribuir-lhe sempre cessionáriostrue
Ashish Kamble
Não posso falar com a qualidade da solução, mas isso passa com êxito os parâmetros para mim.
James
6

Sempre que você precisar se comunicar entre os filhos e os pais em qualquer nível inferior, é melhor usar o contexto . No componente pai, defina o contexto que pode ser chamado pelo filho, como

No componente pai no seu caso, componente 3

static childContextTypes = {
        parentMethod: React.PropTypes.func.isRequired
      };

       getChildContext() {
        return {
          parentMethod: (parameter_from_child) => this.parentMethod(parameter_from_child)
        };
      }

parentMethod(parameter_from_child){
// update the state with parameter_from_child
}

Agora, no componente filho (componente 5 no seu caso), apenas diga a esse componente que ele deseja usar o contexto de seu pai.

 static contextTypes = {
       parentMethod: React.PropTypes.func.isRequired
     };
render(){
    return(
      <TouchableHighlight
        onPress={() =>this.context.parentMethod(new_state_value)}
         underlayColor='gray' >   

            <Text> update state in parent component </Text>              

      </TouchableHighlight>
)}

você pode encontrar o projeto Demo no repo

Rajan Twanabashu
fonte
Eu não posso capaz de entender esta resposta, você pode explicar mais sobre isso
Ashish Kamble
5

Parece que só podemos passar dados de pai para filho quando a reação promove o fluxo de dados unidirecional, mas para fazer o pai atualizar-se quando algo acontece em seu "componente filho", geralmente usamos o que é chamado de "função de retorno de chamada".

Passamos a função definida no pai para o filho como "adereços" e chamamos essa função do filho, acionando-a no componente pai.


class Parent extends React.Component {
  handler = (Value_Passed_From_SubChild) => {
    console.log("Parent got triggered when a grandchild button was clicked");
    console.log("Parent->Child->SubChild");
    console.log(Value_Passed_From_SubChild);
  }
  render() {
    return <Child handler = {this.handler} />
  }
}
class Child extends React.Component {
  render() {
    return <SubChild handler = {this.props.handler}/ >
  }
}
class SubChild extends React.Component { 
  constructor(props){
   super(props);
   this.state = {
      somethingImp : [1,2,3,4]
   }
  }
  render() {
     return <button onClick = {this.props.handler(this.state.somethingImp)}>Clickme<button/>
  }
}
React.render(<Parent />,document.getElementById('app'));

 HTML
 ----
 <div id="app"></div>

Neste exemplo, podemos fazer com que os dados passem de SubChild -> Child -> Parent passando a função para seu filho direto.

Vishal Bisht
fonte
4

Eu usei uma resposta mais bem avaliada nesta página muitas vezes, mas enquanto aprendia o React, encontrei uma maneira melhor de fazer isso, sem vincular e sem a função embutida dentro dos adereços.

Basta olhar aqui:

class Parent extends React.Component {

  constructor() {
    super();
    this.state={
      someVar: value
    }
  }

  handleChange=(someValue)=>{
    this.setState({someVar: someValue})
  }

  render() {
    return <Child handler={this.handleChange} />
  }

}

export const Child = ({handler}) => {
  return <Button onClick={handler} />
}

A chave está em uma função de seta:

handleChange=(someValue)=>{
  this.setState({someVar: someValue})
}

Você pode ler mais aqui . Espero que isso seja útil para alguém =)

Александр Немков
fonte
Isso faz muito mais sentido para mim. Obrigado!
Brett Rowberry 24/01
3

-Podemos criar ParentComponent e com o método handleInputChange para atualizar o estado ParentComponent. Importe o ChildComponent e passamos dois props do componente pai para o filho, ou seja, a função e a contagem de handleInputChange.

import React, { Component } from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.handleInputChange = this.handleInputChange.bind(this);
    this.state = {
      count: '',
    };
  }

  handleInputChange(e) {
    const { value, name } = e.target;
    this.setState({ [name]: value });
  }

  render() {
    const { count } = this.state;
    return (
      <ChildComponent count={count} handleInputChange={this.handleInputChange} />
    );
  }
}
  • Agora criamos o arquivo ChildComponent e salvamos como ChildComponent.jsx. Este componente é sem estado porque o componente filho não possui um estado. Usamos a biblioteca prop-types para verificar o tipo de adereços.

    import React from 'react';
    import { func, number } from 'prop-types';
    
    const ChildComponent = ({ handleInputChange, count }) => (
      <input onChange={handleInputChange} value={count} name="count" />
    );
    
    ChildComponent.propTypes = {
      count: number,
      handleInputChange: func.isRequired,
    };
    
    ChildComponent.defaultProps = {
      count: 0,
    };
    
    export default ChildComponent;
Sachin Metkari
fonte
Como isso funciona quando a criança tem um filho que afeta o suporte de seus pais?
Bobort 28/01/19
3

Se esse mesmo cenário não estiver espalhado em todos os lugares, você poderá usar o contexto do React, especialmente se não desejar introduzir toda a sobrecarga que as bibliotecas de gerenciamento de estado apresentam. Além disso, é mais fácil aprender. Mas tenha cuidado, você pode usá-lo demais e começar a escrever códigos incorretos. Basicamente, você define um componente Container (que manterá e manterá esse estado para você), tornando todos os componentes interessados ​​em escrever / ler esses dados em seus filhos (não necessariamente filhos diretos)

https://reactjs.org/docs/context.html

Você também pode usar o React comum corretamente.

<Component5 onSomethingHappenedIn5={this.props.doSomethingAbout5} />

passar doSomethingAbout5 até o Componente 1

    <Component1>
        <Component2 onSomethingHappenedIn5={somethingAbout5 => this.setState({somethingAbout5})}/>
        <Component5 propThatDependsOn5={this.state.somethingAbout5}/>
    <Component1/>

Se esse é um problema comum, você deve começar a pensar em mudar todo o estado do aplicativo para outro lugar. Você tem algumas opções, as mais comuns são:

https://redux.js.org/

https://facebook.github.io/flux/

Basicamente, em vez de gerenciar o estado do aplicativo em seu componente, você envia comandos quando algo acontece para atualizar o estado. Os componentes extraem o estado desse contêiner também para que todos os dados sejam centralizados. Isso não significa que não é mais possível usar o estado local, mas esse é um tópico mais avançado.

Pato Loco
fonte
2

então, se você deseja atualizar o componente pai,

 class ParentComponent extends React.Component {
        constructor(props){
            super(props);
            this.state = {
               page:0
            }
        }

        handler(val){
            console.log(val) // 1
        }

        render(){
          return (
              <ChildComponent onChange={this.handler} />
           )
       }
   }


class ChildComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
             page:1
        };
    }

    someMethod = (page) => {
        this.setState({ page: page });
        this.props.onChange(page)
    }

    render() {
        return (
       <Button
            onClick={() => this.someMethod()} 
       > Click
        </Button>
      )
   }
}

Aqui onChange é um atributo com o método "manipulador" vinculado à sua instância. passamos o manipulador de método para o componente da classe Child, para receber via propriedade onChange em seu argumento props.

O atributo onChange será definido em um objeto de props como este:

props ={
onChange : this.handler
}

e passado para o componente filho

Portanto, o componente Filho pode acessar o valor do nome no objeto props como este props.onChange

É feito através do uso de render adereços.

Agora, o componente Filho possui um botão "Clique" com um evento onclick definido para chamar o método manipulador passado a ele via onChnge em seu objeto de argumento props. Portanto, agora this.props.onChange in Child mantém o método de saída na classe Parent Reference e créditos: Bits and Pieces

Preetham NT
fonte
Desculpe ... por atraso, Aqui o onChange é um atributo com o método "manipulador" vinculado à sua instância. passamos o manipulador de método para o componente da classe Child, para receber via propriedade onChange em seu argumento props. O atributo onChange será definido em um objeto props como este: props = {onChange: this.handler} e passado para o componente filho Para que o componente Child possa acessar o valor do nome no objeto props como este props.onChange o uso de adereços de renderização. Referência e créditos: [ blog.bitsrc.io/…
Preetham NT
0

É assim que eu faço.

type ParentProps = {}
type ParentState = { someValue: number }
class Parent extends React.Component<ParentProps, ParentState> {
    constructor(props: ParentProps) {
        super(props)
        this.state = { someValue: 0 }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, someValue: value})
    }

    render() {
        return <div>
            <Child changeFunction={this.handleChange} defaultValue={this.state.someValue} />
            <p>Value: {this.state.someValue}</p>
        </div>
    }
}

type ChildProps = { defaultValue: number, changeFunction: (value: number) => void}
type ChildState = { anotherValue: number }
class Child extends React.Component<ChildProps, ChildState> {
    constructor(props: ChildProps) {
        super(props)
        this.state = { anotherValue: this.props.defaultValue }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, anotherValue: value})
        this.props.changeFunction(value)
    }

    render() {
        return <div>
            <input onChange={event => this.handleChange(Number(event.target.value))} type='number' value={this.state.anotherValue}/>
        </div>
    }
}
EU SOU O MELHOR
fonte
0

A maioria das respostas dadas acima é para projetos baseados em React.Component. Se você estiver usando useStateas atualizações recentes da biblioteca React, siga esta resposta

leeCoder
fonte
-3
<Footer 
  action={()=>this.setState({showChart: true})}
/>

<footer className="row">
    <button type="button" onClick={this.props.action}>Edit</button>
  {console.log(this.props)}
</footer>

Try this example to write inline setState, it avoids creating another function.
suresh
fonte
Ele causar problema de desempenho: stackoverflow.com/q/36677733/3328979
Arashsoft