Как обновить состояние родителя в React?

351

Моя структура выглядит следующим образом:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

Компонент 3 должен отображать некоторые данные в зависимости от состояния Компонента 5. Поскольку реквизиты являются неизменяемыми, я не могу просто сохранить его состояние в Компоненте 1 и переслать его, верно? И да, я читал о редуксе, но не хочу им пользоваться. Я надеюсь, что это можно решить просто с помощью реакции. Я ошибся?

wklm
источник
20
супер-просто: передайте parent-setState-Function через свойство дочернему компоненту: <MyChildComponent setState = {p => {this.setState (p)}} /> В дочернем компоненте вызовите его через this.props. SetState ({myObj, ...});
Марсель Энникс
@MarcelEnnix, Ваш комментарий сэкономит мое время. Спасибо.
Динит Минура
<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />если вы собираетесь использовать этот хак, убедитесь, что вы поддерживаете обратный вызов.
Barkermn01
4
Передача обратного вызова для установки состояния родителя - это действительно плохая практика, которая может привести к проблемам с обслуживанием. Это нарушает инкапсуляцию и делает компоненты 2 4 и 5 тесно связанными с 1. Если вы пойдете по этому пути, то вы не сможете повторно использовать эти дочерние компоненты где-либо еще. Лучше иметь специальные реквизиты, чтобы дочерние компоненты могли инициировать события всякий раз, когда что-то происходит, тогда родительский компонент будет правильно обрабатывать это событие.
Пато Локо
@MarcelEnnix, почему круглые скобки this.setState(p)? Я пытался без них, и это, кажется, работает (я очень новичок в React)
Biganon

Ответы:

679

Для связи ребенок-родитель вы должны передать функцию, устанавливающую состояние от родителя к ребенку, например

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

Таким образом, ребенок может обновить состояние родителя с помощью вызова функции, переданной с помощью реквизита.

Но вам придется переосмыслить структуру ваших компонентов, потому что, как я понимаю, компоненты 5 и 3 не связаны между собой.

Одно из возможных решений - заключить их в компонент более высокого уровня, который будет содержать состояние обоих компонентов 1 и 3. Этот компонент будет устанавливать состояние более низкого уровня с помощью подпорок.

Иван
источник
6
зачем вам this.handler = this.handler.bind (this), а не только функция-обработчик, которая устанавливает состояние?
chemook78
35
@ chemook78 в ES6 Методы классов React не привязываются к классу автоматически. Так что, если мы не добавим this.handler = this.handler.bind(this)конструктор, thisвнутри handlerфункции будет указана функция замыкания, а не класс. Если вы не хотите связывать все свои функции в конструкторе, есть еще два способа справиться с этим, используя функции стрелок. Вы можете просто написать обработчик кликов как onClick={()=> this.setState(...)}, или вы можете использовать инициализаторы свойств вместе с функциями стрелок, как описано здесь babeljs.io/blog/2015/06/07/react-on-es6-plus в разделе «Функции стрелок»
Иван
1
Вот пример этого в действии: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb
5
Это все имеет смысл, почему e.preventDefault? и это требует jquery?
Винсент Бускарелло
1
Быстрый вопрос, запрещает ли это присвоение местного состояния ребенку?
ThisGuyCantEven
50

Я нашел следующее рабочее решение для передачи аргумента функции onClick от дочернего к родительскому компоненту:

Версия с передачей метода ()

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

Посмотрите на JSFIDDLE

Версия с передачей функции 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")
    );
}

Посмотрите на JSFIDDLE

Римский
источник
Это хорошо! Не могли бы вы объяснить этот кусок: <ChildB handleToUpdate = {handleToUpdate.bind(this)} />почему пришлось связывать снова?
датчанин
@Dane - вы должны связать контекст этого с родительским, чтобы при thisвызове внутри дочернего элемента thisотносилось к состоянию родителя, а не к дочернему состоянию. Это закрытие во всей красе!
Кейси
@ Кейси, но разве мы не делаем это в конструкторе? И разве этого недостаточно?
датчанин
Ты совершенно прав! Я пропустил это. Да, если вы уже сделали это в конструкторе, тогда все готово!
Кейси
Ты легенда, приятель! Это будет держать компоненты хорошо автономными без необходимости создавать родительские компоненты для обработки обмена состояниями
adamj
13

Я хочу поблагодарить наиболее одобренный ответ за то, что он дал мне представление о моей собственной проблеме, в основном, о ее изменении с помощью функции стрелки и передачи параметра из дочернего компонента:

 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')}/ >
  }
}

Надеюсь, это кому-нибудь поможет.

Ardhi
источник
Что особенного в функции стрелки над прямым вызовом?
Ашиш
@AshishKamble функции thisin in arrow относятся к контексту родителя (т.е. Parentклассу).
CPHPython
Это дублированный ответ. Вы можете добавить комментарий к принятому ответу и упомянуть об этой экспериментальной функции, чтобы использовать функцию стрелки в классе.
Arashsoft
10

Мне нравится ответ относительно передачи функций, это очень удобная техника.

С другой стороны, вы также можете достичь этого с помощью pub / sub или с помощью варианта диспетчера, как это делает Flux . Теория очень проста: компонент 5 отправляет сообщение, которое ожидает компонент 3. Компонент 3 затем обновляет свое состояние, которое запускает повторную визуализацию. Это требует компонентов с состоянием, которые, в зависимости от вашей точки зрения, могут быть или не быть анти-паттерном. Я против них лично и предпочел бы, чтобы что-то еще слушало рассылки и изменяло состояние с самого начала (Redux делает это, но добавляет дополнительную терминологию).

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

Распределитель связывает с Flux очень просто, он просто регистрирует обратные вызовы и вызывает их, когда происходит любая диспетчеризация, проходя через содержимое диспетчеризации (в приведенном выше кратком примере payloadс диспетчеризацией нет, просто идентификатор сообщения). Вы можете очень легко адаптировать это к традиционному pub / sub (например, используя EventEmitter из событий или другую версию), если это имеет для вас больше смысла.

Мэтт Стайлс
источник
Мои компоненты Reacts «работают» в браузере, как в официальном учебнике ( facebook.github.io/react/docs/tutorial.html ). Я попытался включить Flux с browserify, но браузер говорит, что Dispatcher не найден :(
wklm
2
Синтаксис, который я использовал, был синтаксис модуля ES2016, который требует транспиляции (я использую Babel , но есть и другие, трансформацию babelify можно использовать с browserify), она переносится вvar Dispatcher = require( 'flux' ).Dispatcher
Matt Styles
8

Я нашел следующее рабочее решение для передачи аргумента функции onClick от дочернего к родительскому компоненту с параметром:

родительский класс:

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

детский класс:

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. Парниан
источник
2
Может кто-нибудь сказать, является ли это приемлемым решением (особенно интересно передать параметр, как предложено).
Иланс
param1это просто отображение на консоли, а не назначение ему всегда назначаетсяtrue
Ашиш
Я не могу говорить о качестве решения, но это успешно проходит парам для меня.
Джеймс
6

Если вам когда-нибудь понадобится общаться между ребенком и родителем на любом уровне ниже, тогда лучше использовать контекст . В родительском компоненте определите контекст, который может вызываться дочерним элементом, например

В родительском компоненте в вашем случае компонент 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
}

Теперь в дочернем компоненте (компонент 5 в вашем случае), просто скажите этому компоненту, что он хочет использовать контекст своего родителя.

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

Вы можете найти демо-проект в репо

Раджан Тванабашу
источник
Я не могу понять этот ответ, можете ли вы объяснить больше об этом
Ашиш
5

Кажется, что мы можем только передавать данные от родителя к потомку, так как реагирует, продвигает однонаправленный поток данных, но чтобы самому обновлять родительский элемент, когда что-то происходит в его «дочернем компоненте», мы обычно используем то, что называется «функцией обратного вызова».

Мы передаем функцию, определенную в родительском элементе, дочернему элементу как «props» и вызываем эту функцию из дочернего элемента, вызывая ее в родительском компоненте.


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>

В этом примере мы можем передать данные из SubChild -> Child -> Parent, передав функцию ее непосредственному Child.

Вишал Бишт
источник
4

Я использовал ответ с этой страницей с самым высоким рейтингом много раз, но изучая React, я нашел лучший способ сделать это, без привязки и без встроенной функции внутри подпорок.

Просто посмотрите здесь:

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

Ключ находится в функции стрелки:

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

Вы можете прочитать больше здесь . Надеюсь это кому-нибудь пригодится =)

Александр Немков
источник
Это имеет гораздо больше смысла для меня. Спасибо!
Бретт Роуберри
3

-Мы можем создать ParentComponent и с помощью метода handleInputChange обновить состояние ParentComponent. Импортируйте ChildComponent, и мы передаем два реквизита от родительского к дочернему компоненту, т.е. функции и count.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} />
    );
  }
}
  • Теперь мы создаем файл ChildComponent и сохраняем как ChildComponent.jsx. Этот компонент не имеет состояния, потому что дочерний компонент не имеет состояния. Мы используем библиотеку prop-types для проверки типов реквизита.

    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;
Сачин Меткари
источник
Как это работает, когда у ребенка есть ребенок, который влияет на опору его родителя?
Боборт
3

Если этот сценарий не распространяется повсеместно, вы можете использовать контекст React, особенно если вы не хотите вводить все накладные расходы, которые представляют библиотеки управления состоянием. Кроме того, легче учиться. Но будьте осторожны, вы можете злоупотребить этим и начать писать плохой код. По сути, вы определяете компонент Контейнер (который будет хранить и сохранять этот фрагмент состояния для вас), делая все компоненты, заинтересованные в записи / чтении этого фрагмента данных, его дочерними (не обязательно прямыми дочерними).

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

Вместо этого вы также можете использовать обычный React.

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

передать doSomethingAbout5 до компонента 1

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

Если это обычная проблема, вы должны начать думать о переносе всего состояния приложения в другое место. У вас есть несколько вариантов, наиболее распространенными из которых являются:

https://redux.js.org/

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

По сути, вместо управления состоянием приложения в вашем компоненте вы отправляете команды, когда что-то происходит для обновления состояния. Компоненты также извлекают состояние из этого контейнера, поэтому все данные централизованы. Это не значит, что я больше не могу использовать локальное состояние, но это более сложная тема.

Пато Локо
источник
2

Итак, если вы хотите обновить родительский компонент,

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

Здесь onChange является атрибутом с методом «обработчик», связанным с его экземпляром. мы передали обработчик метода компоненту класса Child, чтобы получить через свойство onChange его аргумент props.

Атрибут onChange будет установлен в объекте props следующим образом:

props ={
onChange : this.handler
}

и передается дочернему компоненту

Таким образом, дочерний компонент может получить доступ к значению имени в объекте props, как этот props.onChange

Это делается с помощью реквизита рендера.

Теперь у дочернего компонента есть кнопка «Click» с событием onclick, установленным для вызова метода-обработчика, переданного ему через onChnge в его объекте аргумента props. Так что теперь this.props.onChange в Child содержит метод вывода в родительском классе Reference и кредиты: биты и кусочки

Preetham NT
источник
Извините за задержку, здесь onChange - это атрибут с методом «обработчика», привязанным к его экземпляру. мы передали обработчик метода компоненту класса Child, чтобы получить через свойство onChange его аргумент props. Атрибут onChange будет установлен в объекте props следующим образом: props = {onChange: this.handler} и передан дочернему компоненту. Таким образом, дочерний компонент может получить доступ к значению имени в объекте props, как этот props.onChange. использование рендера реквизита. Ссылка и кредиты: [ blog.bitsrc.io/…
Preetham NT
0

Вот как я это делаю.

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>
    }
}
Я ЛУЧШИЙ
источник
0

Большинство ответов, приведенных выше, относятся к проектам на основе React.Component. Если вы используете useStateв последних обновлениях библиотеки React, следуйте этому ответу

leeCoder
источник
-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.
Суреш
источник
Это вызывает проблемы с производительностью: stackoverflow.com/q/36677733/3328979
Arashsoft