1

I'm trying to make a set of inputs that can be duplicated or removed.

I've found and used a combination of this : https://jsfiddle.net/69z2wepo/36745/ and this (because the code above does not handle removal) : https://codepen.io/lichin-lin/pen/MKMezg

I may not need to point at a specific input since in my interface you should always only be adding a new one if the one before is filled (i'll set up the condition later) and therefore only deleting the last. So I'm fine with a simple counter as a solution to all this (though I'll need 3-4 counters for the different input types).

/* ************************************* */
/* ********       IMPORTS       ******** */
/* ************************************* */
import React, { Component } from 'react';
import { Card, CardBlock, Button, InputGroup, Input } from 'reactstrap';
import ProviderInfos from '../ProviderInfos/ProviderInfos';

/* ************************************* */
/* ********      VARIABLES      ******** */
/* ************************************* */

const count = 0;

/* ************************************* */
/* ********      COMPONENT      ******** */
/* ************************************* */
export default class SearchExtendedComponent extends Component {

    constructor(props) {
        super(props);
        this.state = { inputList: [] };
        this.incrementCount = this.incrementCount.bind(this);
        this.decrementCount = this.decrementCount.bind(this);
    }

    incrementCount() {
      const inputList = this.state.inputList;
        this.setState({
            count: this.state.count + 1,
            inputList: inputList.concat(<Input key={count} />),
        });
    }
    decrementCount() {
      const inputList = this.state.inputList;
        this.setState({
            count: this.state.count - 1,
            inputList: inputList.concat(<Input key={count} />),
        });
    }
    render() {
        return (
            <Card>
                <CardBlock className="main-table">
                    <InputGroup>
                        <Input placeholder="Type1" />
                        <ProviderInfos />
                    </InputGroup>
                    {/* THE IDEA IS TO HAVE AN ADD AND REMOVE BUTTON FOR EACH TYPE */}
                    <InputGroup className="add-more">
                        <button onClick={this.incrementCount}>Add input</button>
                        {this.state.inputList}
                    </InputGroup>
                    <InputGroup>
                        <Input placeholder="Type2" />
                        <ProviderInfos />
                    </InputGroup>
                    <InputGroup>
                        <Input placeholder="Type3" />
                        <ProviderInfos />
                    </InputGroup>
                    <Button color="secondary">Options</Button>{' '}
                    <Button id="btn">Exécuter</Button>
                </CardBlock>
            </Card>
        );
    }
}

I get an error in the console :

Warning: flattenChildren(...): Encountered two children with the same key, `1:$0`. Child keys must be unique; when two children share a key, only the first child will be used.

and this seems fitting with what occurs :

Only the first new input is added after that the "add" button ceases to work.

As you can see I'm currently declaring the input within the functions ( or at least it seems to me like I am... Is that not what const inputList = this.state.inputList; does?) and I think that's the issue I should be declaring it next to "count" IMO but my attempts to do that :

const inputList = this.state.inputList;

const propTypes = { inputList: React.PropTypes.inputList, };

have resulted in the app not loading at all : "Cannot read property 'state' of undefined".

Not only that but that doesn't seem like clean refactored code to me since I'm doing it twice and keep in mind I'm going to have to duplicate this code even more since both the add and delete functions (and buttons) will have to be there for three to four different input types down the line.

UPDATE : moved the remove button part to another question : React : Add / Remove components with buttons : Remove not working

tatsu
  • 2,316
  • 7
  • 43
  • 87
  • add count in the initial state. `this.state = { inputList: [] , count :0};` remove the `const count = 0;` line – abdul Aug 07 '17 at 09:45
  • @abdul is the initial state a default function of sorts that I4m supposed to add? I don't see it in my own code. – tatsu Aug 07 '17 at 09:50
  • you're setting the initial state of your component just below this line `super(props)` inside your constructor. just add `count : 0` to it like `this.state = { inputList: [] , count :0};` – abdul Aug 07 '17 at 09:53
  • @abdul ok but with `const count = 0;` removed I get `Uncaught ReferenceError: count is not defined` – tatsu Aug 07 '17 at 09:55
  • I think you're getting that b/c you're using in `` change this also to `` – abdul Aug 07 '17 at 09:58
  • thanks @abdul ! you're a champ! that worked! type your answer and I'll mark you as solution. Although I'd also like to know since I'm completely new to react code and only know how to do this in Java : how to refactor this code for all 4 input types. – tatsu Aug 07 '17 at 10:01
  • I can't consider this fixed anymore I prematurely thought I had delete but I don't I don't want to reopen another question just for delete. – tatsu Aug 07 '17 at 14:16
  • You shouldn't modify your question each time you're facing a new issue. It's going to complicate the answer too. Since it's somehow unrelated to the 1st Q you had, Create a new question with the code you currently have and the error you're getting. – abdul Aug 07 '17 at 14:30
  • @abdul ok https://stackoverflow.com/questions/45549361/react-add-remove-components-with-buttons-remove-not-working remarked – tatsu Aug 07 '17 at 14:33

3 Answers3

2

The key that is passed in <Input key={count} /> should be unique. You shouldn't have two items in the list with the same key value.

In your case, when you do 2 subsequent increment and decrement, you end up with same count value. That means same key repeated for different <Input/> tag

console.log the count to check if its unique. The key 0 is used twice in the array, that's what the error means.

Atty
  • 691
  • 13
  • 20
  • ok. but how do I not give 0 to the "key" whatever that is. I feel like I've done nothing to make that happen? `count` should be `this.state.count + 1,` .... 0+1 =1 and so on.... – tatsu Aug 07 '17 at 09:46
  • dont use count at all in input tag. Initialise another variable and keep incrementing it in case of both increment and decrement – Atty Aug 07 '17 at 09:51
  • ok thanks but I can't jsut put `inputList: inputList,` at some point I have to in some desciption tell inputList to "+1" do I not? what does that look like? I really am a beginner in react. – tatsu Aug 07 '17 at 09:58
  • If you really need the count, then use it as another parameter. So your tag could look like `` – Atty Aug 07 '17 at 10:02
  • thanks! abdul suggested something similar and it worked :) – tatsu Aug 07 '17 at 10:03
  • Good to know! But i wonder how. If you increment once and decrement again, the count value goes back to the same. Ideally you should end up with same warning. – Atty Aug 07 '17 at 10:06
2

Remove const count and initialize a count variable in state.

constructor(props) {
        super(props);
        this.state = { inputList: [], count: 0 };
        this.incrementCount = this.incrementCount.bind(this);
        this.decrementCount = this.decrementCount.bind(this);
    }

Then use this.state.count as key in input element:

incrementCount() {
      const inputList = this.state.inputList;
        this.setState({
            count: this.state.count + 1,
            inputList: inputList.concat(<Input key={this.state.count} />),
        });
    }
    decrementCount() {
      const inputList = this.state.inputList;
        this.setState({
            count: this.state.count - 1,
            inputList: inputList.concat(<Input key={this.state.count} />),
        });
    }
Prakash Sharma
  • 15,542
  • 6
  • 30
  • 37
  • the decrement method does not work for me. I get : `warning.js:36 Warning: flattenChildren(...): Encountered two children with the same key, `0:$0`. Child keys must be unique; when two children share a key, only the first child will be used` and the "remove" button makes the list of inputs bigger by one and then stops doing anything. – tatsu Aug 07 '17 at 12:29
2

add count in the initial state.

this.state = { inputList: [] , count :0}; 

Then remove this line const count = 0;

Since you're using count as your key change this also to.

<Input key={this.state.count} />

I made a jssfiddle for another question, the concept is similar so it might be helpful.

abdul
  • 1,531
  • 1
  • 14
  • 29
  • I don't see any button that calls the decrement method. add this ` {this.state.inputList} ` – abdul Aug 07 '17 at 10:17
  • thanks! could you explain how a refactored version of this with say 4 different cards would look? – tatsu Aug 07 '17 at 10:17
  • I had added the button already but it doesn't trigger. why do you add a second {this.state.inputList} – tatsu Aug 07 '17 at 10:22
  • ohh that's by mistake, if you've the button it should work as expected. – abdul Aug 07 '17 at 10:27
  • ideally, you could have a different list and count for each input type. Also a different handler(increment and decrement) for each input type that increments the corresponding input type count. – abdul Aug 07 '17 at 10:31
  • I get `warning.js:36 Warning: flattenChildren(...): Encountered two children with the same key, `0:$0`. Child keys must be unique; when two children share a key, only the first child will be used`. as for the different method for each type thanks that's how I'd planed to do it : ) – tatsu Aug 07 '17 at 12:14
  • I'm thinking it's because I can't "concat" a smaller list. I can't figure out what method I should be using. – tatsu Aug 07 '17 at 12:22
  • solved here : https://stackoverflow.com/questions/45549361/react-add-remove-components-with-buttons-remove-not-working – tatsu Aug 09 '17 at 15:37