Почему мой реквизит обновляется до того, как я обновляю состояние? - PullRequest
0 голосов
/ 11 июня 2019

Я пытаюсь изменить входные данные внутри класса GrandChild и таблицы начальной загрузки внутри родительского класса *.Пользователь может изменить входные данные внутри ** GrandChild класса , а затем сохранить его, чтобы изменения были видны в таблице начальной загрузки в Родительский класс ;Тем не менее, я вижу странное поведение, когда мои реквизиты меняются до того, как я вызываю .onChange (это мое спасение).Я считаю, что это приводит к тому, что мои входные данные не сохраняются или не устанавливаются должным образом.

Данные, передаваемые по иерархии: GrandParent => Parent => Child => GrandChild

Это происходит в Функция handleSave () дочернего класса:

export class Child extends React.Component {
    constructor(props){
        this.state  = {
            data:this.props.data
        }
    }


    handleChange = (name, value) => {
        this.setState((prevState) => {
            let newState = {...prevState};
            newState.data.dataList[0][name] = value;  // data
            return newState;
        });
    };

    handleSave(){
        let dataList = this.state.data.dataList.slice();
        console.log("dataList state-dataList:", dataList);
        console.log("dataList before onChange 2:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
        this.props.onChange("dataList", dataList);
        console.log("dataList onChange 3:", this.props.data.dataList); //Same as dataList or this.state.data.dataList
    }

    render() {
    return (
        <div>
            <GrandChild data={this.state.data} onChange={this.handleChange} />
        </div>
    )
}

this.props.onChange дочернего класса отправляется обратно в Родительский класс :

export class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
    }

    columns = [
      {dataField: '..', text: '...' }, 
      {dataField: '..', text: '...' },  
      {dataField: '..', text: '...' }, 
      {dataField: '..', text: '...'}];


    handleChange = (name, value) => {
        this.props.onChange(name, value);
    };

    render() {
        return (
            <div>
                <BootstrapTable
                    hover
                    condensed={true}
                    bootstrap4={true}
                    keyField={'id'}
                    data={this.props.data.dataList}
                    columns={this.columns}
                />
                <Child data={this.props.data} onChange={this.handleChange} />
            </div>


        );

    }

}

Затем Родительский класс this.props.onChange * отправляется GrandParent Class :

export class GrandParent extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        data: {...this.props.location.state.data}
    };
    this.handleChange = this.handleChange.bind(this);
}   

    handleChange = (name, value) => {
        this.setState((prevState) => {
            let newState = {};
            let data = Object.assign({}, prevState.data);
            data[name] = value;
            newState.data = data;
            return newState;
        });
    };


render() {
return (
    <div>
        <Form>
        <Parent data={this.state.data} onChange={this.handleChange} />
        </Form>
    </div>
)
}

Это класс GrandChild :

export class GrandChild extends React.Component {
constructor(props) {
    super(props);
    this.handleInputChange = this.handleInputChange.bind(this);
}   

handleInputChange = (event) => {
    const target = event.target;
    const value = target.type === 'checkbox' ?
        target.checked :
        target.value;
    const name = target.name;
    this.props.onChange(name, value);
};


render() {
return (
    <div>
        <Form.Row>
            <Form.Group as={Col}>
                <Form.Label>Label Name</Form.Label>
                <Form.Control name="labelName" value={this.props.data.[0]labelName || ""} //ignore the index for now
                              onChange={this.handleInputChange}/>
            </Form.Group>
        </Form.Row>
    </div>
)
}
}

Я ожидал, что console.logs () из dataLists будет другим;тем не менее, они дают тот же самый точный объект, прежде чем он даже запустит this.props.onChange ("dataList", dataList);

Потенциально третий dataList console.log может быть таким же, какdataList состояния из-за асинхронности setState.

1 Ответ

0 голосов
/ 11 июня 2019

Похоже, основная проблема в том, что вы изменяете состояние / реквизит в Child:

    handleChange = (name, value) => {
        this.setState((prevState) => {
            // {...prevState} makes a shallow copy of `prevState`
            let newState = {...prevState};
            // Next you're modifying data deep in `newState`,
            // so it's mutating data in the `dataList` array,
            // which updates the source of truth for both props and state.
            newState.data.dataList[0][name] = value;
            return newState;
        });
    };

Один из способов сделать это (избежать мутации) выглядит так:

handleChange = (name, value) => {
  this.setState(prevState => ({
    data: {
      ...prevState.data,
      dataList: [
        {
          ...prevState.data.dataList[0],
          [name]: value
        },
        ...prevState.data.dataList.slice(1)
      ]
    }
  }));
};

Если это более многословно, чем вы хотели бы, вы можете использовать библиотеку типа immutable-js.

Другая проблема, которая может привести к вашим ошибкам, - это копирование реквизита в состояние. Эта статья объясняет, почему это плохо: https://overreacted.io/writing-resilient-components/#dont-stop-the-data-flow-in-rendering

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

Также, handleChange в Grandparent можно написать более просто:

    handleChange = (name, value) => {
      this.setState(prevState => ({
        data: {
          ...prevState.data,
          [name]: value
        }
      }))
    };
...