Wisdom1
Wisdom1

Reputation: 129

React - How to loop and update values in object model?

I have an object model pattern as below

   this.state = {
        data: {
            audi: {
                 engine: '2.5',
                 gearbox: 'auto',
                 fuel: 'petrol'
             },
             bmw: {
                 engine: '3.0',
                 gearbox: 'auto',
                 fuel: 'petrol'
             },
             merc: {
                 engine: '6.3',
                 gearbox: 'manual',
                 fuel: 'petrol'
             }
         }
    }

My goal is to:

  1. Loop through the data and render
  2. Map value changes in the HTML to the corresponding keys in the this.state.data object

I haven't used an Array, as it has to look like above object pattern when posting it.

At the moment here is how I am trying to do this:

//To render
Object.entries(this.state.data).map((x,index) =>
    <p>x.key</p> //Trying to reach 'audi' with x.key

    // Render each KPV in object
    {x.key}: <input key={index} onChange={this.handleChange} value={x.value}/>
)

Then to update the corresponding values in the same state model:

// To update the state model with changed values
setData = (key, val) => {
    this.setState(state => ({
        data: {
            ...state.data,
            obj: { [key]: val },
            //Don't know how to map to state properly!
        }
    }));
};

// Handle input change
handleChange = key => e => {
    this.setData(key, e.target.value);
};

Please could anyone advise me on this approach, many thanks :)

Upvotes: 4

Views: 3837

Answers (3)

Jamiec
Jamiec

Reputation: 136074

First you need to get the rendering right, which means looping over all the keys (car makers) and then all the property/value pairs. You can use destructuring arrays to make this easier

Object.entries(someObject).map( ([key,value]) => .... )

Heres rendering:

render() {
    return Object.entries(this.state.data).map(([maker, props]) => {
      return (
        <div>
          <h2>{maker}</h2>
          {Object.entries(props).map(([key, value], index) => {
            return (
              <div>
                {key}:{" "}
                <input
                  key={index}
                  onChange={this.handleChange(maker, key)}
                  defaultValue={value}
                />
              </div>
            );
          })}
        </div>
      );
    });
  }

You'll note I changed your handleChange method to pass the car maker and the property being edited - this can be passed to setData :

// To update the state model with changed values
  setData = (maker, key, val) => {
    this.setState((state) => ({
      data: {
        ...state.data,
        [maker]: {
          ...state.data[maker],
          [key]: val
        }
        //Don't know how to map to state properly! Now you do!
      }
    }));
  };

  // Handle input change
  handleChange = (maker, key) => (e) => {
    this.setData(maker, key, e.target.value);
  };

Live working example: https://codesandbox.io/s/react-playground-forked-dw9sg

Upvotes: 3

siaznik
siaznik

Reputation: 516

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { 
      data: {
        audi: { model: "A7", transmission: "AT" },
        merc: { model: "GLA", transmission: "MT" },
        bmw: { model: "M3", transmission: "AT" },
      },
    }
  }
  update(make, detail, value) {
    let data = this.state.data
    data[make][detail] = value
    console.log(`Updating ${make} ${detail} with ${value}`)
    this.setState({ data: data })
  }
  handleChange(make, detail) {
    return e => { this.update(make, detail, e.target.value) }
  }
  renderDetails(details) {
    return details.map(d => (<div><span>{d.key}:</span> <input value={d.model} onChange={this.handleChange(d.key, "model")} /> <input value={d.transmission} onChange={this.handleChange(d.key, "transmission")} /></div>))
  }
  render() {
    const details = Object.entries(this.state.data).map(d => ({ key: d[0], ...d[1] }))
    return <div>{this.renderDetails(details)}</div>
  }
}

ReactDOM.render((<App />), document.getElementById("app"))
body { font-family: monospace; }
span { display: block; font-weight: bold; }
input { width: 50px; }
<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="app"></div>

Upvotes: 1

Punith K
Punith K

Reputation: 666

You are not passing key and event object from the input callback in the first place, your code should be like,

The display logic looks wrong to me,

Object.entries(this.state.data).map((x, index) =>
    /* 
     here x is an array and it looks like 
     ["audi", {engine: '2.5', gearbox: 'auto', fuel: 'petrol}
    */
     // You need to access the data in the second index
     // if you want to print each value of the object
    <p>x[1].engine</p>
    <p>x[1].gearbox</p>
    <p>x[1].fuel</p> 

    // Render each KPV in object
    {x[0]}: <input key={index} onChange={(e) => this.handleChange(x[0], e)} value={x[1].engine}/>
)

and handleChange callback should look like, don't curry them.

handleChange = (key, e) => {
    this.setData(key, e.target.value);
};

And I would suggest using a utility like castArray from lodash, https://lodash.com/docs/4.17.15#castArray to convert the object to an array so that you can easily work with it.

Upvotes: 2

Related Questions