简体   繁体   中英

React not rendering correctly after state change

I have a simple form. You click an "Add Item" button and a textbox appears. On blur, the text entered in the textbox gets added to a state variable array. Click the "Add Item" button again, another textbox appears and so on.

For each textbox, there is also a "Remove Item" button. When this button is clicked, the current item is removed from the array and the current textbox is removed from the page.

class App extends React.Component {
 constructor(props) {
   super(props);
   this.state = {
     items: []
   }
 }

 addItem() {
  this.setState({        
    items: [...this.state.items, []]
    }
  )
}

removeItem(index) {
  //var items = this.state.items;
  var items = [...this.state.items];

  items.splice(index, 1);

  this.setState({
    items: items
  })
}

changeItem(e, index) {
  var items = this.state.items;  

  items[index] = e.target.value;

  this.setState({
    items: items
  })
}

  render() {
    return (
      <div>
        {
          this.state.items.map((item, index) => {
            return (
              <React.Fragment key={index}>
                <hr />
                <Row>
                    <Col column sm="8">
                      <Form.Control
                        type="text"
                        name="item"                                              
                        onBlur={(e) => this.changeItem(e, index)}
                      />
                    </Col>
                  </Row>
                  <Row>
                    <Col column sm="8">
                      <Button
                        onClick={() => this.removeItem(index)}
                        variant="link"
                        size="sm">
                          Remove Item
                      </Button>                                                            
                    </Col>                              
                  </Row>         
              </React.Fragment>
            )
          })
        }
        <br />
          <Button
            onClick={(e) => this.addItem(e)}
            variant="outline-info">Add item
          </Button>
      </div>
    )
  }
}

The problem I have is, although the array is successfully modified in removeItem(index) , the textbox that gets removed from the page is always the last one added, not the one that should be removed. For example:

  1. Click "Add Item", type: aaa items: ['aaa']
  2. Click "Add Item", type: bbb items: ['aaa', 'bbb']
  3. Click "Add Item", type: ccc items: ['aaa', 'bbb', 'ccc']
  4. Click "Remove Item" under aaa . Items gets successfully updated: items: ['bbb', 'ccc']

The page should show a textbox with bbb and one with ccc . But it shows:

在此处输入图像描述

How can I remove the correct textbox from the page?

There are a few problems with your code:

  1. Firstly, you are directly changing the this.state without using this.setState() in the changeItem function, I have changed it to var items = [...this.state.items];
  2. You are using index as key for a list item, that you are rendering using the this.state.items.map((item, index) => {...}) in <React.Fragment key={index}> . This key should be a unique identifier for the list item, usually, it is the unique id from the database. Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity . However, in your case, since you don't have unique ids for the items, I am creating those using uuid module. learn more about keys: https://reactjs.org/docs/lists-and-keys.html and https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318
  3. I have removed column attribute from the Col component, because it was giving some warning
  4. Since, now I am adding a unique id to each list item, I have changed the structure of the this.state.items from array of strings to array of objects, where each object has a id and data, where data is text
  5. you were using Form.Control component without using value prop. Suppose you added one list item, wrote something in the input, clicked on the add item button again. at this point, since you changed focus, the onBlur event would trigger, and your this.state.items would change accordingly, so far, so good. BUT now when it re-renders the whole thing again, it is going to re-render the Form.Control component, but without the value prop, this component will not know what data to show, hence it will render as empty field. Hence, I added value prop to this component
  6. Since I added value prop in Form.Control component, react now demands that I add onChange event to the component, otherwise it will render as read-only input, hence I changed onBlur to onChange event. There is no need for onBlur to change the state value, when onChange is already there.

Here is the finished code:

import React from "react";
import { v4 } from 'uuid';
import { Button, Row, Col, Form } from "react-bootstrap";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: []
    };
  }

  addItem() {
    this.setState({
      items: [...this.state.items, {data: "", id: v4()}]
    });
  }

  removeItem(index) {
    console.log("dbg1", index);
    //var items = this.state.items;
    var items = [...this.state.items];

    items.splice(index, 1);

    this.setState({
      items: items
    });
  }

  changeItem(e, index) {
    console.log("dbg2", this.state.items);
    var items = [...this.state.items];

    items[index].data = e.target.value;

    this.setState({
      items: items
    });
  }

  render() {
    console.log("dbg3", this.state.items);
    return (
      <div>
        {this.state.items.map((item, index) => {
          return (
            <React.Fragment key={item.id}>
              <hr />
              <Row>
                <Col sm="8">
                  <Form.Control
                    type="text"
                    name="item"
                    value={item.data}
                    onChange={(e) => this.changeItem(e, index)}
                    // onBlur={(e) => this.changeItem(e, index)}
                  />
                </Col>
              </Row>
              <Row>
                <Col sm="8">
                  <Button
                    onClick={() => this.removeItem(index)}
                    variant="link"
                    size="sm"
                  >
                    Remove Item
                  </Button>
                </Col>
              </Row>
            </React.Fragment>
          );
        })}
        <br />
        <Button onClick={(e) => this.addItem(e)} variant="outline-info">
          Add item
        </Button>
      </div>
    );
  }
}

export default App;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM