David Johns
David Johns

Reputation: 1734

How to update state array dynamically in React?

I have the following component with a function which can dynamically add New elements in my form. I have a button and when clicking on the button, a new form field will be added. Now I need to keep this new field's value in a new state and send it to the API. To send all the new fields, I planned to set a single state as array and push new field values after enter them. After adding few new fields, I need something like this,

this.state.other [
   {
      description: "field 1 description",
      amount: "120$"
   },
   {
      description: "field 2 description",
      amount: "180$"
   },
   {
      description: "field 3 description",
      amount: "156$"
   },
]

This is what I have tried so far:

let dataId = 0;

class ClientIntakeModal extends Component {
    constructor(props) {
        super(props);

        this.allOtherCosts = [];

        this.state = {
            otherCosts: this.allOtherCosts,
            other: [],
        }
        this.otherDescInputHandle = this.otherDescInputHandle.bind(this);
    }

    appendData = () => {
        dataId = dataId + 1;
        this.allOtherCosts.push(
            <div className="halfWidth jobCostMain" key={dataId}>
                <div>
                    <div className="jobDescAdded">
                        <TextField
                            id={'costDescription'}
                            name={'description'}
                            type="text"
                            value={this.state.other.description}
                            onChange={this.otherDescInputHandle}
                        />
                    </div>
                    <div className="jobCostAdded">
                        <TextField
                            id={'costAmount'}
                            name={'amount'}
                            type="number"
                            value={this.state.other.amount}
                            onChange={this.otherDescInputHandle}
                        />
                    </div>
                </div>
            </div>
        );
        this.setState({
            otherCosts: this.allOtherCosts
        });
    }

    otherDescInputHandle(event) {
        const target = event.target;
        const value = target.value;
        const name = target.name;

        this.setState({
            other: [
                ...this.state.other,
                {
                    [name]:value
                },
            ]
        });
    }

    render() {
      return(
           <div>
             <div id="addNewUser" className="addNewUser" onClick={this.appendData}>
                 <span>+</span>
             </div>
             {this.allOtherCosts}
           </div>
      )
    }
}

The problem is, I'm receiving something like follows

this.state.other [
   {
      description: "f",
   },
   {
      description: "fi",
   },
   {
      description: "fie",
   },
   {
      description: "fiel",
   },
]

How can I get the correct state here?

Upvotes: 0

Views: 3722

Answers (2)

gazdagergo
gazdagergo

Reputation: 6721

In general I recommend the following approach to update a collection's item immutable. It is important to use id for the items, which can be a dynamically generated unique id.

class App extends React.Component {
  state = {
    someArray: [
      { id: 1, value: 'a' },
      { id: 2, value: 'b' },
      { id: 3, value: 'c' }
    ]
 }

  setValue = (id, value) => {
    this.setState(prevState => ({
      someArray: prevState.someArray.map(item => (
        id === item.id ? { ...item, value } : item
      ))
    }))
  }
  
  render(){
    return (
      <div>
        <ul>
          {this.state.someArray.map(({value}) => <li>{value}</li>)}
        </ul>
        <button onClick={() => this.setValue(2, 'foo')}>Set value</button>
      </div>
    )

  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 1

Carlos Adriano
Carlos Adriano

Reputation: 68

i have some considerations to your code and that will better him. otherDescInputHandle function, use desctruction, like that:

const { target, value, name } = event;

and do that:

const { other } = this.state;
/* your ideia */
this.setState({ other });

In your appendData function, in dataId get the length of array

dataId = this.allOtherCosts.length;

and in onChange of your textfield, pass the event and the dataId

onChange={e => this.otherDescInputHandle(e, dataId)}

so, in the otherDescInputHandle you have the position of the field on array, and when you go to change the value, you cand access the correctly position of array:

const { other } = this.state;
other[dataId].description = value;

Try this ideia. I guess this will solve your problem.

And please, try to use only state variables and manipulating they. Is perfectly possible to solve your problem without using this.allOtherCosts = [];

Upvotes: 1

Related Questions