4 votes

Comment régler l'état d'un objet JSON array imbriqué dans une entrée mappée ?

J'ai un JSON avec plusieurs catégories, chaque catégorie ayant un nom et un ensemble de champs de saisie avec leur propre nom et valeur.

Comment puis-je utiliser setState pour mettre à jour les champs de valeur de chaque onChange ? Les catégories et les champs sont rendus à l'aide de map() .

J'arrive à le faire fonctionner sans les champs imbriqués mais pas avec. J'apprécie toute aide.

Fichier JSON

[{
    "catName": "Category 1",
    "fields": [
      {
        "name": "field 1",
        "amount": "0"
      },
      {
        "name": "field 2",
        "amount": "0"
      }
    ]
  },
  {
    "catName": "Category 2",
    "fields": [
      {
        "name": "field 1",
        "amount": "0"
      },
      {
        "name": "field 2",
        "amount": "0"
      }
}]

Main.js

import React, { Component } from "react";
import Category from "./Category";
import sampleData from "./sampleData";

class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: sampleData
    };
  }

  handleChange = e => {
    this.setState({ ???  });
  };

  render() {
    return (
      <div>
        {this.state.list.map(item => (
          <Category
            id={item.catName}
            name={item.catName}
            key={item.catName}
            list={item}
            handleChange={this.handleChange}
          />
        ))}
      </div>
    );
  }
}

export default Main;

Catégorie.js

import React from "react";
import Item from "./Item";

const Category = ({ name, list, handleChange }) => {
  return (
    <div className="section">
      <h3>{name}</h3>
      {list.fields.map(item => (
        <Item
          id={item.name}
          name={item.name}
          key={item.name}
          list={item}
          handleChange={handleChange}
        />
      ))}
    </div>
  );
};

export default Category;

Item.js

import React from "react";

const Item = ({ list, handleChange }) => {
  return (
    <div className="item">
      <label className="label">{list.name}</label>
      <input
        name={list.name}
        id={list.name}
        className="input"
        type="text"
        onChange={handleChange}
        value={list.amount}
      />
    </div>
  );
};

export default Item;

2voto

julekgwa Points 4014

Passez la catégorie et l'index de l'article à votre handleChange fonction. Utilisez ces indices pour mettre à jour le bon élément dans le tableau. Évitez la mutation d'état en ne faisant pas

// state mutation
this.state.list[categoryIndex].fields[fieldIndex].amount = e.target.value

fonction handleChange

handleChange = (e, categoryIndex, itemIndex) => {

  const { list } = this.state;

  const fields = [...list[categoryIndex].fields.slice(0, itemIndex),
  Object.assign({}, list[categoryIndex].fields[itemIndex], { amount: e.target.value }),
  ...list[categoryIndex].fields.slice(itemIndex + 1)
  ]

  this.setState({
    list: [...list.slice(0, categoryIndex),
    Object.assign({}, list[categoryIndex], { fields }),
    ...list.slice(categoryIndex + 1)
    ]
  })
}

Composant de l'élément, ajoutez la catégorie et l'index du fichier comme props.

import React from "react";

const Item = ({ list, handleChange, categoryIndex, itemIndex, value }) => {
  return (
    <div className="item">
      <label className="label">{list.name}</label>
      <input
        name={list.name}
        id={list.name}
        className="input"
        type="text"
        value={value}
        onChange={(e) => handleChange(e, categoryIndex, itemIndex)}
      />
    </div>
  );
};

export default Item;

Composante de la catégorie

import React from "react";
import Item from "./Item";

const Category = ({ name, list, handleChange, categoryIndex }) => {
  return (
    <div className="section">
      <h3>{name}</h3>
      {list.fields.map((item, index) => (
        <Item
          id={item.name}
          name={item.name}
          key={item.name}
          list={item}
          categoryIndex={categoryIndex}
          itemIndex={index}
          value={item.amount}
          handleChange={handleChange}
        />
      ))}
    </div>
  );
};

export default Category;

DEMO

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>

<script type="text/babel">

const Item = ({ list, handleChange, categoryIndex, itemIndex, value }) => {
  return (
    <div className="item">
      <label className="label">{list.name}</label>
      <input
        name={list.name}
        id={list.name}
        className="input"
        type="text"
        value={value}
        onChange={(e) => handleChange(e, categoryIndex, itemIndex)}
      />
    </div>
  );
};

const Category = ({ name, list, handleChange, categoryIndex }) => {
  return (
    <div className="section">
      <h3>{name}</h3>
      {list.fields.map((item, index) => (
        <Item
          id={item.name}
          name={item.name}
          key={item.name}
          list={item}
          categoryIndex={categoryIndex}
          itemIndex={index}
          value={item.amount}
          handleChange={handleChange}
        />
      ))}
    </div>
  );
};

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      name: 'React',
      show: false,
      list: [
        {
          "catName": "Category 1",
          "fields": [
            {
              "name": "field 1",
              "amount": "0"
            },
            {
              "name": "field 2",
              "amount": "0"
            }
          ]
        },
        {
          "catName": "Category 2",
          "fields": [
            {
              "name": "field 1",
              "amount": "0"
            },
            {
              "name": "field 2",
              "amount": "0"
            }
          ]
        }
      ]
    };
  }

  handleChange = (e, categoryIndex, itemIndex) => {

    const { list } = this.state;

    const fields = [...list[categoryIndex].fields.slice(0, itemIndex),
    Object.assign({}, list[categoryIndex].fields[itemIndex], { amount: e.target.value }),
    ...list[categoryIndex].fields.slice(itemIndex + 1)
    ]

    this.setState({
      list: [...list.slice(0, categoryIndex),
      Object.assign({}, list[categoryIndex], { fields }),
      ...list.slice(categoryIndex + 1)
      ]
    })
  }

  show = () => {
    this.setState({
      show: true
    })
  }

  render() {
    return (
      <div>
        {this.state.list.map((item, index) => (
          <Category
            id={item.catName}
            name={item.catName}
            key={item.catName}
            categoryIndex={index}
            list={item}
            handleChange={this.handleChange}
          />
        ))}
        <br />
        <button onClick={this.show}>Show changes</button>
        {this.state.show &&
          <pre>
          {JSON.stringify(this.state.list, null, 4)}
          </pre>
        }
      </div>
    );
  }
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);
</script>

1voto

Tom Scholz Points 488

Votre JSON n'est pas valide. Vous avez également oublié de vérifier si list contient déjà des données.

Essayez ça :

Dans votre handleChange veillez à utiliser un balisage JSON correct. Vous avez oublié la fermeture ]} :

 this.setState({ list: [{
      "catName": "Category 1",
      "fields": [
        {
          "name": "field 1",
          "amount": "0"
        },
        {
          "name": "field 2",
          "amount": "0"
        }
      ]
    },
    {
      "catName": "Category 2",
      "fields": [
        {
          "name": "field 1",
          "amount": "0"
        },
        {
          "name": "field 2",
          "amount": "0"
        }
    ]}
  ]})

Dans la méthode de rendu de votre Main vérifie si la liste est un tableau et si sa longueur est supérieure à 0. Cela évitera toute erreur de rendu, dans le cas où une valeur qui n'est pas de type tableau est définie.

   {Array.isArray(this.state.list) && this.state.list.length < 0 && this.state.list.map(item => (
      <Category
        id={item.catName}
        name={item.catName}
        key={item.catName}
        list={item}
        handleChange={this.handleChange}
      />
    ))}

Veillez également à définir un tableau vide dans le constructeur de votre classe principale :

constructor(props) {
    super(props);
    this.state = {
      list: []
    };
 }

0voto

Naor Tedgi Points 424

commençons par le bas

  1. vous devez fournir à Item.js l'id de sa catégorie parente en changeant son id en id={${name},${item.name}} . il serait également intéressant d'ajouter l'événement onClick pour nettoyer les données précédentes.

  2. le composant de la catégorie doit fournir un identifiant au composant de l'article.

  3. ensuite, dans le composant principal, après avoir obtenu le droit d'accès au json, vous pouvez utiliser la méthode createNewData pour créer un nouvel objet.

voici le résultat :

Main.js

import React, { Component } from "react";
import Category from "./Category";
import sampleData from "./sampleData";

class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: sampleData
    };
  }

  createNewData = (mainAccess, property, value) => {
    let newData = sampleData;
    newData.forEach(category => {
      if (category["catName"] === mainAccess) {
        debugger;
        category["fields"].forEach(item => {
          if (item["name"] === property) {
            console.log(item["amount"]);
            item["amount"] = value;
          }
        });
      }
    });
    return newData
  };

  handleChange = e => {
    const propertyAccess = e.target.id.split(",");
    const newData = this.createNewData(propertyAccess[0],propertyAccess[1],e.target.value)
    this.setState({list:newData})
  };

  render() {
    return (
      <div>
        {this.state.list.map(item => (
          <Category
            id={item.catName}
            name={item.catName}
            key={item.catName}
            list={item}
            handleChange={this.handleChange}
          />
        ))}
      </div>
    );
  }
}

export default Main;

Item.js

import React from "react";

const Item = ({ list, handleChange ,id}) => {

    return (
    <div className="item">
      <label className="label">{list.name}</label>
      <input
        name={list.name}
        id={id}
        className="input"
        type="text"
        onChange={handleChange}
        onClick={e=>e.target.value=""}
        value={list.amount}

      />
    </div>
  );
};

export default Item;

Catégorie.js

import React from "react";
import Item from "./Item";

const Category = ({ name, list, handleChange }) => {
  return (
    <div className="section">
      <h3>{name}</h3>
      {list.fields.map(item => (
        <Item
          id={`${name},${item.name}`}
          name={item.name}
          key={item.name}
          list={item}
          handleChange={handleChange}
        />
      ))}
    </div>
  );
};

export default Category;

0voto

ayteq Points 18

Mettez votre code à jour comme suit

import React, { Component } from "react";
import Category from "./Category";
import sampleData from "./sampleData";

class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: sampleData
    };
  }

  handleChange = (e, fieldName, catName) => {
    //get list from state
    const { list } = this.state

    //this returns the related item's index, I assume that all cats have a unique name, otherwise you should use unique values such as IDs
    const targetCatIndex = list.findIndex(item => item.catName === catName) 

    //find related field index
    const targetFieldIndex = list[targetCatIndex].fields.findIndex(item => item.name === fieldName)

    //update the field and assign to state
    list[targetCatIndex].fields[targetFieldIndex].amount = e.target.value

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

  render() {
    return (
      <div>
        {this.state.list.map(item => (
          <Category
            id={item.catName}
            name={item.catName}
            key={item.catName}
            list={item}            
            handleChange={this.handleChange}
          />
        ))}
      </div>
    );
  }
}

export default Main;

import React from "react";
import Item from "./Item";

const Category = ({ name, list, handleChange }) => {
  return (
    <div className="section">
      <h3>{name}</h3>
      {list.fields.map(item => (
        <Item
          id={item.name}
          name={item.name}
          key={item.name}
          list={item}
          // pass field and cat referance with input event
          handleChange={(e, fieldName) => handleChange(e, fieldName, name) } 
        />
      ))}
    </div>
  );
};

export default Category;

import React from "react";

const Item = ({ list, handleChange }) => {
  return (
    <div className="item">
      <label className="label">{list.name}</label>
      <input
        name={list.name}
        id={list.name}
        className="input"
        type="text"
        //pass related field referance here
        onChange={(e) => handleChange(e, list.name)}
        value={list.amount}
      />
    </div>
  );
};

export default Item;

Et voici le Démonstration de travail

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X