1

I want to repeat the same line of fields when i click on the plus button. I tried to implement this functionality based on an state attribute plus that changed to true when i click on the button then i check if this state attribute is true? add the Fields : null. but it doesn't work and i think i am missing some concept so please some help!

the component state:

  this.state = {
            plus : false 
        }

the plusHandler:

plus = (e)=>{
    this.setState({
        plus: true,
     });
    }

in the render:

     <div className="form-row">
                <div className="form-group col-md-5">
                    <label htmlFor="cRelation">Relation</label>
                    <select name="cRelation" defaultValue={''} id="cRelation" className="form-control">
                        <option disabled value=''> select relation</option>
                        {relationList.map(item => (
                            <option key={item} value={item}>{item}</option>
                         )
                        )}
                    </select>
                </div>
                <div className="form-group col-md-6">
                    <label htmlFor="withConcept">withConcept</label>
                    <select name="withConcept" defaultValue={''} id="withConcept" className="form-control">
                        <option value='' disabled> select concept</option>
                        {(conceptList|| []).map(item => (
                        <option key={item.conceptId} value={item.conceptId}>{item.conceptName}</option>
                    ))}

                    </select>

                </div>
                <div className="form=group align-self-sm-center mt-2">
                    <button type="button" className="btn btn-sm btn-outline-success m-2" onClick={this.plus}>+</button>
                    <button type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
                </div>
            </div>

{this.state.plus? 
                <div className="form-row">
                <div className="form-group col-md-5">
                    <label htmlFor="cRelation">Relation</label>
                    <select name="cRelation" defaultValue={''} id="cRelation" className="form-control">
                        <option disabled value=''> select relation</option>
                        {relationList.map(item => (
                            <option key={item} value={item}>{item}</option>
                         )
                        )}
                    </select>
                </div>
                <div className="form-group col-md-6">
                    <label htmlFor="withConcept">withConcept</label>
                    <select name="withConcept" defaultValue={''} id="withConcept" className="form-control">
                        <option value='' disabled> select concept</option>
                        {(conceptList|| []).map(item => (
                        <option key={item.conceptId} value={item.conceptId}>{item.conceptName}</option>
                    ))}

                    </select>

                </div>
                <div className="form=group align-self-sm-center mt-2">
                    <button type="button" className="btn btn-sm btn-outline-success m-2"  onClick={this.plus}>+</button>
                    <button type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
                </div>
            </div>
    :null }

this is the output i want:

enter image description here

3
  • I understand the expected behavior, but can you provide additional information such as what the current behavior is and what your onClick handler looks like for the + button? Commented Jan 22, 2020 at 16:50
  • @CoreyLarson thanks for replaying! i edited it, this code just add once and that is it Commented Jan 22, 2020 at 16:55
  • I see, so it was working to add one additional row, but you wanted to add multiple rows. It also seems you've got your answer :) Commented Jan 22, 2020 at 17:10

3 Answers 3

1

I'd think of it not as add/remove input fields, but rather as managing your form state to maintain necessary elements visibility.

As long as you're going to access values, selected in those input fields (e.g. upon form submit), instead of using boolean flag, you may need to store dynamic form rows within your state as array of following structure:

[
   {rowId:..., selectedOptions:{relation:..., concept...}},
   ...
]

For simplicity sake, I'd also re-design your dynamic form rows as a separate component.

With that, I'd attach onClick() event handlers of add/remove buttons within row component to callbacks of parent form component that will append/remove array items within its state, thus making corresponding row components appear/disappear.

You may inquiry following live-snippet for complete demonstration of that concept:

const { useState } = React,
      { render } = ReactDOM
      
const relations = ['relation1', 'relation2', 'relation3'],
      concepts = ['concept1', 'concept2', 'concept3']

const FormRow = ({rowId, selectedOptions, onSelect, onAdd, onRemove}) => {
  const handleChange = e => onSelect(rowId, e.target.getAttribute('param'), e.target.value)
  return (
    <div>
      <label>Relation:
        <select param="relation" onChange={handleChange} value={selectedOptions.relation||''}>
          <option value="" disabled>select relation</option>
          {
            relations.map((rel,key) => <option {...{key}} value={rel}>{rel}</option>)
          }
        </select>
      </label>
      <label>With Concept:        
        <select param="concept" onChange={handleChange} value={selectedOptions.concept||''}>
          <option value="" disabled>select concept</option>
          {
            concepts.map((con,key) => <option {...{key}} value={con}>{con}</option>)
          }
        </select>
      </label>
      <button type="button" onClick={onAdd}>+</button>
      <button type="button" onClick={() => onRemove(rowId)}>-</button>
    </div>
  )
}

const Form = () => {
  const [rows, setRows] = useState([{rowId:0, selectedOptions:{}}]),
        onAddRow = () => {
          const maxRowId = Math.max(...rows.map(({rowId}) => rowId))
          setRows([...rows, {rowId: maxRowId+1, selectedOptions:{}}])
        },
        onRemoveRow = id => setRows(rows.filter(({rowId}) => rowId != id)),
        onSelectRow = (id, param, val) => {        
          const rowsCopy = [...rows],
                item = rowsCopy.find(({rowId}) => rowId == id)
                Object.assign(item, {selectedOptions:{...item.selectedOptions, [param]:val}})
          setRows(rowsCopy)
        }
  return (
    <form onSubmit={e => (e.preventDefault(), console.log(rows))}>
      {
        rows.map(({rowId, selectedOptions}, key) => (
          <FormRow 
            {...{key, rowId, selectedOptions}}
            onAdd={onAddRow}
            onRemove={onRemoveRow}
            onSelect={onSelectRow}
          />
        ))
      }
    <input type="submit" value="Submit" />
    </form>
  )
}

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

Sign up to request clarification or add additional context in comments.

3 Comments

Can i do this without React Hooks or ? and sorry for my simple question but i am new to react
Sure, it can be done with class components. But I can't imagine the reason why would you do that in this particular case. Function components are more concise and easier to read.
You're welcome. If it works for you, don't forget to upvote-accept ;)
0

You need a list with the items to render. When the user click in plus button you need add a new element.

Example:

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = { items: [{}], relationList: [], conceptList: [] };
    }
    addItem = () => {
        var { items } = this.state;
        items.push({});
        this.setState({ items });
    }

    removeItem = (index) => {
        var { items } = this.state;
        items.splice(index, 1);
        this.setState({ items });
    }

    render() {
        var { items, conceptList, relationList } = this.state;
        return (
            <div>
                {items.map((rowItem, k) => (
                    <div key={k} className="form-row">
                        <div className="form-group col-md-5">
                            <label htmlFor={`cRelation${k}`}>Relation</label>
                            <select name={`cRelation${k}`} defaultValue={''} id={`cRelation${k}`} className="form-control">
                                <option disabled value=''> select relation</option>
                                {relationList.map(item => (
                                    <option key={item} value={item}>{item}</option>
                                )
                                )}
                            </select>
                        </div>
                        <div className="form-group col-md-6">
                            <label htmlFor={`withConcept${k}`}>withConcept</label>
                            <select name={`withConcept${k}`} defaultValue={''} id={`withConcept${k}`} className="form-control">
                                <option value='' disabled> select concept</option>
                                {(conceptList || []).map(item => (
                                    <option key={item.conceptId} value={item.conceptId}>{item.conceptName}</option>
                                ))}
                            </select>
                        </div>
                        <div className="form=group align-self-sm-center mt-2">
                            <button onClick={this.addItem} type="button" className="btn btn-sm btn-outline-success m-2">+</button>
                            <button onClick={() => this.removeItem(k)} type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
                        </div>
                    </div>
                ))}
            </div>
        );
    }
}

3 Comments

Above solution doesn't work properly: removing rows doesn't work as expected (regardless of which - button is clicked, last row is always removed); 'concept' <select> field has no <option>'s; <select> elements of multiple rows have same id attribute, which disrupts DOM structure (sneaked in from OP's code)
Nope you didn't fix none of critical app behavior issues, you just appended unnecessarily complicated unique id assignment, which is not needed in this case, whatsoever. I have revoked my downvote, even though the issue still persists, you might want to check out my answer for ideas to get your code fixed (upvotes are welcome ;)
@JulioJavier i know i was only asking for the plus functionality only and it did work thank you but as Yevgen siad it didn't work when it came to the remove ! splice is not working as it suppose to for some reasons!
0

Instead of Boolean , use an integer to denote the number of rows like below . plus handler will increment the count .

this.state = {
                i: 1
            }

Plus Handler

plus = (e) => {
            this.setState({
                i: this.state.i + 1
            });
        }

Render function :

 rowfunction() {
        return (<div className="form-row">
            <div className="form-group col-md-5">
                <label htmlFor="cRelation">Relation</label>
                <select name="cRelation" defaultValue={''} id="cRelation" className="form-control">
                    <option disabled value=''> select relation</option>

                </select>
            </div>
            <div className="form-group col-md-6">
                <label htmlFor="withConcept">withConcept</label>
                <select name="withConcept" defaultValue={''} id="withConcept" className="form-control">
                    <option value='' disabled> select concept</option>


                </select>

            </div>
            <div className="form=group align-self-sm-center mt-2">
                <button type="button" className="btn btn-sm btn-outline-success m-2 " onClick={this.plus}>+</button>
                <button type="button" className="btn btn-sm btn-outline-danger pr-2">-</button>
            </div>
        </div>)
    }
    render() {
        var rows = [];
        for (let index = 0; index < this.state.i; index++) {
            rows.push(this.rowfunction())
        }
        return rows;

    }

1 Comment

How can i do remove also for the - button ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.