1

CONTEXT

I'm trying to dynamically add input fields in a react component while still keeping track of the states in the parent component (to later call a method using them).

PROBLEM

Not sure how to go about it:

  1. Without needing to define a fixed number of states (urlpath1, urlpath2, etc) in the parent component.
  2. Without defining a fixed number of <Input/> grand children components in the child component.

This is my parent component - this one needs to capture the state (or the values) of the input elements of the children/grandchildren components, regardless of how many such <Input> fields I have.

import AddConditionSelect from './AddConditionSelect.jsx';

class AddConditionDashboard extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      //Form Steps States
      step: 1,
      //AddConditionSelect States
      conditionTitle: '',
      conditionType: ''
    };
    // bind the context for the user input event handler
    // so we can use `this` to reference `AddConditionDashboard`
    this.onUserInputChange = this.onUserInputChange.bind(this);
    this.onUserClick = this.onUserClick.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

//TODO: REFACTOR - Can we refactor both these events into one to be used?
  onUserInputChange(event) {
    this.setState({
     conditionTitle: event.target.value
    });
  }

  onUserClick(event) {
    this.setState({
      conditionType:event.target.value
    });
  }

  onSubmit(event) {
    event.preventDefault;
    const {create, segmentId} = this.props;
    const conditionTitle = this.state.conditionTitle;
    const conditionType = this.state.conditionType;

    create(conditionTitle, conditionType, segmentId);
    //TODO: CALLBACK IF STATEMENT - Check with a callback function if create ran sucessfully, only then do nextste();

    if (conditionType == 'urlPath') {
      this.setState({
          step: 21
        });
    } else if (conditionType == 'timeOnSite') {
      this.setState({
          step: 22
        });
    };

  }

  render() {


    const {error, segmentId} = this.props;

    switch (this.state.step) {
      //SELECT CONDITION
      case 1: return (
                <div>

                {error ? <Message inverted={true}  rounded={true}  theme="error">{error}</Message>  : null}

              <AddConditionSelect
                segmentId={segmentId}
                onUserClick={this.onUserClick}
                conditionTitle={this.state.conditionTitle}
                onUserInputChange={this.onUserInputChange}
              />


              <PanelFooter theme="default">
                <Button
                    backgroundColor="primary"
                    color="white"
                    inverted={true}
                    rounded={true}
                    onClick={(event) => this.onSubmit(event)}
                  >
                    Next Step
                </Button>
              </PanelFooter>


                </div>
              );
      //ADD URL PATH
      case 21: return (
                    <div>

                    <AddConditionCURLPath 'something here' />

                    <PanelFooter theme="default">
                      <Button
                          backgroundColor="primary"
                          color="white"
                          inverted={true}
                          rounded={true}
                          onClick={(event) => this.onSubmit(event)}
                        >
                          Next Step
                      </Button>
                    </PanelFooter>

                    </div>

                  );

        //ADD TIME ON SITE
        case 22: return (
                      <div>

                        <AddConditionSomethignElse  />

                      <PanelFooter theme="default">
                        <Button
                            backgroundColor="primary"
                            color="white"
                            inverted={true}
                            rounded={true}
                            onClick={(event) => this.onSubmit(event)}
                          >
                            Next Step
                        </Button>
                      </PanelFooter>

                      </div>


                    );

    }//End switch

    //end render
  }

}

export default AddConditionDashboard;

This is my child component - here I need to dynamically add new ` fields, values of which need to be transmitted to the parent component.

    class AddConditionCURLPath extends React.Component {

  render() {
    const {error} = this.props;

    return (
      <div>

        <Panel theme="info">

        {error ? <Message inverted={true}  rounded={true}  theme="error">{error}</Message>  : null}
        <Container>
          Configure URL Path - What Pages A Visitor Needs to Visit to meet this conditino
            <Divider />

            <Input
              value={this.props.urlPath}
              label=""
              type="text"
              buttonLabel="Add Condition"
              name="add_segment"
              onChange={this.props.onUserInputChange}
              placeholder="Condition Title"
            />

            <Input
              value={this.props.urlPath}
              label=""
              type="text"
              buttonLabel="Add Condition"
              name="add_segment"
              onChange={this.props.onUserInputChange}
              placeholder="Condition Title"
            />

            <Button
                backgroundColor="primary"
                color="white"
                inverted={true}
                rounded={true}
                onClick={(event) => this.addInputField(event)}
              >
                Add Another Input Field
            </Button>


          </Container>

        </Panel>

      </div>
    );
  }

}

export default AddConditionCURLPath;
6
  • Is this a repost? I think I remember seeing this one before. :) Are you using redux in your app? If so you could consider using redux-form to track and manage your form state. Commented Jun 1, 2016 at 21:03
  • Not using Redux in the app. Doubt that it's a repost as I just stumbled upon this problem... Commented Jun 1, 2016 at 21:04
  • Ah, then you may be in luck. Try searching stackoverflow for the problem. I am almost positive someone asked a nearly identical question. I'll see if it's in my history. Commented Jun 1, 2016 at 21:06
  • @ctrlplusb, searching stack is the first thing one does :) couldn't find anything that's close enough to my setup to be useful. Commented Jun 1, 2016 at 21:08
  • I get that it's dynamic, but where is the number of inputs being defined? Does the parent know? Is the user setting it with an input? Commented Jun 1, 2016 at 21:13

1 Answer 1

4

Hmmm, in your Component that handle the dynamic input you can have a loop of n input to display that can be incremented by addInputField(). This number can be stored in the local state of this component, this can be a simple implementation for the beginning

edit

parent: that hold the values and add/submit method, with summary of values

class Parent extends Component {
  state = {
    inputValues: ['default value 1', 'default value 2']
  }

  addInputField = () => {
    this.setState({
      inputValues: [...this.state.inputValues, '']
    })
  }

  onUserInputChange = (e, i) => {
    let inputValues = [...this.state.inputValues]

    inputValues[i] = e.target.value

    this.setState({
      inputValues
    })
  }

  onSubmit = (e) => {
    e.preventDefault()
    console.log(this.state.inputValues)
  }

  render() {
    const { inputValues } = this.state

    return (
        <form>
          <DynamicInputs inputValues={inputValues}
                         onUserInputChange={this.onUserInputChange}/>
          <button type="button" onClick={this.addInputField}>add</button>
          <ul>
            {inputValues.map((value, i) => (
                <li key={i}>{value}</li>
            ))}
          </ul>
          <button type="submit" onClick={this.onSubmit}>submit</button>
        </form>
    )
  }

}

child: a stateless component of inputs

const DynamicInputs = ({ inputValues, onUserInputChange }) => {
  const inputs = inputValues.map((value, i) => (
      <input key={i}
             type="text"
             defaultValue={value}
             onChange={e => onUserInputChange(e, i)}/>
  ))
  return (
      <div>
        {inputs}
      </div>
  )
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you Michel. There are two concerns - I'd like to keep my child component as a 'dumb' component - I guess I could keep the state in the parent. However, this only answers part of my question - displaying the additional buttons - but not the second part - keeping track of the value of each input field that is being added and passing it back to the parent to then be used in my method. Could you expand your answer to include that if possible?
Hmmm, I see, this is not a complete solution.It's a good point to hold the state to the parent container
great - this is exactly what i needed. Thank you Michael!

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.