React dynamic form bug

I've created script for dynamic form, but there's 2 things which I can't get and my head is exploding right now, hopefully somebody would help me with that.

  1. After creating new fields - I can't remove fields depends on button which was clicked.
  2. And after removing some of those fields, I have this error with fieldsenter image description here
import React from "react";
import {useState , useEffect} from "react";
import ReactDOM  from "react-dom";
import "./index.css";


const Form = () =>{
  const [fieldsLength, fieldsLengthChanger] = useState(1);
  const [fields, fieldsChanger] = useState([{
    id : 1,
    name: "",
    phone: "",
    age: ""
  }])



  return  (
    <>
      <div className="form__wrapper">
        <h2>Form </h2>
        {
          fields.map((elem, index) => {
            return(
              <FormElement {...elem} fields={fields} fieldsChanger={fieldsChanger} fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger}/>
            )
          })
        }
        <AddMore fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger} fields={fields} fieldsChanger={fieldsChanger}/>
      </div>
    </>
  )
}


const FormElement = ({fieldsLength  ,...props}) =>{

  function inputHandler(e, id){
    console.log(e.target.name);
    const values = [...props.fields];
    values[id-1][e.target.name] = e.target.value;
    props.fieldsChanger(values);
  }

  function removeElement(e,id){
    e.preventDefault();
    var arr = [...props.fields];
    const newArray = arr.filter(function(elem,index){
      console.log("index:" , index, "ID : ", id);
      if (index + 1 != id){
        return elem;
      }
    });
    // console.log(newArray);
    // arr.splice(3, 1);
    props.fieldsChanger([])
    props.fieldsChanger(newArray);

    
  }

  return (
    <div className="group__form">
      <div className="form__element">
        <input type="text" value={props.fields.name} name="name" onChange={e => inputHandler(e , props.id)} />
      </div>
      <div className="form__element">
        <input type="text" value={props.fields.phone} name="phone" onChange={e => inputHandler(e , props.id)}/>
      </div>
      <div className="form__element">
        <input type="text" value={props.fields.age} name="age" onChange={e => inputHandler(e , props.id)}/>
      </div>
      {
        fieldsLength > 1 ? <div className="remove__field">
          <a href="#" onClick={e=>removeElement(e , props.id)}>Remove</a>
        </div> : ""
      }
    </div>
  )
}
const AddMore = (props) =>{
  function addMore(){
    props.fieldsLengthChanger(props.fields.length + 1);
    props.fieldsChanger([...props.fields, {id:props.fields.length + 1 , name: "" , phone : "" , age :''} ]);
    
  }
  return (
    <div className="add__more">
      <a href="#" onClick={e=> addMore()}>Add element</a>
    </div>
  )
}


ReactDOM.render(<Form/> , document.getElementById("root"));

Where I'm wrong - would be really helpfull to understand what is the problem

Upvotes: 0

Views: 116

Answers (2)

Liki Crus
Liki Crus

Reputation: 2062

1. You should use the props.id to remove a form instead of using index.

e.g. this must be a bug.

      if (index + 1 != id) {
        return elem;
      }

2. And you should use id by the unique value generation instead of using array's length.

3. You should provide the key prop in the render of the form fields. Otherwise, React can't distinguish forms in rendering. And the key should be unique whenever you add or remove items.

e.g. See the updated code. You need to use elem.id. (Of course, you should generate id in the add method.

       {fields.map((elem, index) => {
          return (
            <FormElement
              key={elem.id}
              {...elem}
              fields={fields}
              fieldsChanger={fieldsChanger}
              fieldsLength={fieldsLength}
              fieldsLengthChanger={fieldsLengthChanger}
            />
          );
        })}

4. You could basically use a timestamp. Please use your own generation logic in the production. You can use uuid generation package.

    {
      id: new Date().getTime(),
      name: "",
      phone: "",
      age: ""
    }

My advices:

  1. You don't need to use this state variable const [fieldsLength, fieldsLengthChanger] = useState(1); Because we can get this value by fields.length.

  2. There are a few problems in terms of components design. e.g. Please try to define addMore() in form then you don't need to pass fields, fieldsChanger as props.

Here is a full working code with your old code commented.

const Form = () => {
  const [fieldsLength, fieldsLengthChanger] = useState(1);
  const [fields, fieldsChanger] = useState([
    {
      id: new Date().getTime(),
      name: "",
      phone: "",
      age: ""
    }
  ]);

  return (
    <>
      <div className="form__wrapper">
        <h2>Form </h2>
        {fields.map((elem, index) => {
          return (
            <FormElement
              key={elem.id}
              {...elem}
              fields={fields}
              fieldsChanger={fieldsChanger}
              fieldsLength={fieldsLength}
              fieldsLengthChanger={fieldsLengthChanger}
            />
          );
        })}
        <AddMore
          fieldsLength={fieldsLength}
          fieldsLengthChanger={fieldsLengthChanger}
          fields={fields}
          fieldsChanger={fieldsChanger}
        />
      </div>
    </>
  );
};

const FormElement = ({ fieldsLength, ...props }) => {
  function inputHandler(e, id) {
    console.log(e.target.name);
    const values = [...props.fields];
    const item = values.find((x) => x.id === props.id);
    if (item) {
      item[e.target.name] = e.target.value;
    }
    // values[id - 1][e.target.name] = e.target.value;
    props.fieldsChanger(values);
  }

  function removeElement(e, id) {
    e.preventDefault();
    var arr = [...props.fields];
    const newArray = arr.filter(function (elem, index) {
      /*console.log("index:", index, "ID : ", id);
      if (index + 1 != id) {
        return elem;
      }*/
      return elem.id !== id;
    });
    // console.log(newArray);
    // arr.splice(3, 1);
    props.fieldsChanger([]);
    props.fieldsChanger(newArray);
  }

  return (
    <div className="group__form">
      <div className="form__element">
        <input
          type="text"
          value={props.fields.name}
          name="name"
          onChange={(e) => inputHandler(e, props.id)}
        />
      </div>
      <div className="form__element">
        <input
          type="text"
          value={props.fields.phone}
          name="phone"
          onChange={(e) => inputHandler(e, props.id)}
        />
      </div>
      <div className="form__element">
        <input
          type="text"
          value={props.fields.age}
          name="age"
          onChange={(e) => inputHandler(e, props.id)}
        />
      </div>
      {fieldsLength > 1 ? (
        <div className="remove__field">
          <a href="#" onClick={(e) => removeElement(e, props.id)}>
            Remove
          </a>
        </div>
      ) : (
        ""
      )}
    </div>
  );
};
const AddMore = (props) => {
  function addMore() {
    props.fieldsLengthChanger(props.fields.length + 1);
    props.fieldsChanger([
      ...props.fields,
      //{ id: props.fields.length + 1, name: "", phone: "", age: "" }
      { id: new Date().getTime(), name: "", phone: "", age: "" }
    ]);
  }
  return (
    <div className="add__more">
      <a href="#" onClick={(e) => addMore()}>
        Add element
      </a>
    </div>
  );
};

Upvotes: 0

Hereblur
Hereblur

Reputation: 2164

Always use key when rendering list.

<FormElement key={elem.id} {...elem} ...

https://reactjs.org/docs/lists-and-keys.html#keys

Upvotes: 1

Related Questions