Search code examples
javascriptreactjsuse-state

How to useState on an array of Objects


Basically, I want to use useState to update an array of objects and at the same time updating the UI by adding new components that i can update through input, but it doesn't seems to work: setup:

    const initialState = {
    rows: [{ id: "R-0", name: "name", type: "text" }],
    formName: "formName",
  };
const [formBuilderRows, setRows] = useState(initialState.rows);

UI Display:

<tbody id="tbody-FormBuilder">
              {formBuilderRows.map((it, index) => {
                return (
                  <FormBuilderRow
                    fieldId={it.id}
                    fieldName={it.name}
                    fieldType={it.type}
                    deleteHandler={deleteItem(index)}
                    handleChange={handleFieldInput}
                  />
                );
              })}
            </tbody>

change handler:

const handleFieldInput = (e) => { let id = e.target.parentElement.parentElement.id;

setRows(
  [...formBuilderRows].map((row) => {
    if (row.id == id) {
      switch (e.target.name) {
        case "name":
          return { ...row, name: e.target.value };

        case "type":
          return { ...row, type: e.target.value };

        default:
          break;
      }
    }
  })
);

};

the FormBuilderRow:

import { useAppContext } from "../context/appContext";
const FormBuilderRow = ({
  fieldName,
  fieldType,
  deleteHandler,
  handleChange,
  fieldId,
}) => {
  const { formBuilderTypeOptions: list } = useAppContext();
  return (
    <tr id={fieldId}>
      <td>
        <input
          type="text"
          name="name"
          className="form-input"
          value={fieldName}
          onChange={handleChange}
          required
        />
      </td>
      <td>
        <select
          className="form-select select-field"
          name="type"
          onChange={handleChange}
        >
          {list.map((itemValue, index) => {
            if (itemValue == fieldType)
              return (
                <option key={index} value={itemValue} selected>
                  {itemValue}
                </option>
              );
            else
              return (
                <option key={index} value={itemValue}>
                  {itemValue}
                </option>
              );
          })}
        </select>
      </td>
      <td>
        <button
          className="btn btn-danger"
          type="button"
          onClick={deleteHandler}
        >
          <svg
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              d="M6.2253 4.81108C5.83477 4.42056 5.20161 4.42056 4.81108 4.81108C4.42056 5.20161 4.42056 5.83477 4.81108 6.2253L10.5858 12L4.81114 17.7747C4.42062 18.1652 4.42062 18.7984 4.81114 19.1889C5.20167 19.5794 5.83483 19.5794 6.22535 19.1889L12 13.4142L17.7747 19.1889C18.1652 19.5794 18.7984 19.5794 19.1889 19.1889C19.5794 18.7984 19.5794 18.1652 19.1889 17.7747L13.4142 12L19.189 6.2253C19.5795 5.83477 19.5795 5.20161 19.189 4.81108C18.7985 4.42056 18.1653 4.42056 17.7748 4.81108L12 10.5858L6.2253 4.81108Z"
              fill="currentColor"
            />
          </svg>
        </button>
      </td>
    </tr>
  );
};

export default FormBuilderRow;

Solution

  • Your changeHandler is not complete because you didnt cover all cases inside .map, and function expression you provided to .map must return something. Rewrite to this:

    setRows(
      [...formBuilderRows].map((row) => {
        if (row.id == id) {
          switch (e.target.name) {
            case "name":
              return { ...row, name: e.target.value };
    
            case "type":
              return { ...row, type: e.target.value };
    
            default:
              return row; // YOU MISSED THIS
          } 
        }
        return row; // AND THIS
      })
    );