0

I have 4 nested components, <CompD> is nested within <CompC> which is nested within <CompB> which is nested within <CompA>.

<CompD> contains a Redux Form with onSubmit={onSubmit} with a bunch of inputs and a Save button with type="submit"

A snippet of <CompD> currently with the button:

const propTypes = {
  firstName: PropTypes.string,
  secondName: PropTypes.string
};

const defaultTypes = {
  firstName = " ",
  secondName = " "
};

const PollForm = ({
  firstName,
  secondName
}) => (
  <Form onSubmit={onSubmit}>
    .
    .
    <button type="submit">
      'Save'
    </button>
  </Form>
);

PollForm.propTypes = propTypes;
PollForm.defaultProps = defaultProps;

export default PollForm;

I want to move this button to <CompA> instead, so a button in <CompA> which behaves the exact same way as the button in <CompD> and submits the Form.

A snippet of <CompA> with a new button added:

const propTypes = {
  onSubmit: PropTypes.func, ...
};

const defaultTypes = {
  onSubmit: () = {}, ...
};

class Config extends Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this); ...
  }

  handleSubmit(data) {
    this.props.onSubmit(data);
  }

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

    return (
      .
      .
      <button onClick={onSubmit}>
        'Save'
      </button>
    )
  }
}

How do I pass the handleSubmit(data) function in <CompA the data from the form Any ideas how I could do this?

4
  • Do you want to get the button in CompA and to keep the form in CompD? Commented Oct 2, 2018 at 15:41
  • you mean clicking on the button in <CompA> and then somehow trigger the onSubmit event of the Form which is in <CompD>, right? Commented Oct 2, 2018 at 15:44
  • @NguyễnThanhTú yes that's correct Commented Oct 2, 2018 at 15:57
  • @IvanBurnaev yep Commented Oct 2, 2018 at 15:57

2 Answers 2

1

You can lift up the form-submission state and logics from <CompD> to <CompA>, and may use the React Context to provide the form states and handlers deep-down to <CompD>, such as this:

    import React from 'react';
    import ReactDOM from 'react-dom';

    const FormContext = React.createContext();

    function CompB({ children }) {
      return <div id="B">{children}</div>;
    }
    function CompC({ children }) {
      return <div id="C">{children}</div>;
    }

    function CompD() {
      return (
        <FormContext.Consumer>
          {({ onSubmit, onChange, name }) => {
            return (
              <form onSubmit={onSubmit}>
                <label>
                  Name:
                  <input type="text" value={name} onChange={onChange} />
                </label>
                <input type="submit" value="submit-input" />
              </form>
            );
          }}
        </FormContext.Consumer>
      );
    }

    class CompA extends React.Component {
      onChange = ({ target: { value } }) => {
        this.setState({ name: value });
      };
      onSubmit = event => {
        event.preventDefault();
        console.log('Submitting name: ', this.state.name);
      };
      state = {
        name: 'defaultName',
        onSubmit: this.onSubmit,
        onChange: this.onChange,
      };
      render() {
        return (
          <FormContext.Provider value={this.state}>
            <CompB>
              <CompC>
                <CompD />
              </CompC>
            </CompB>
            <button name="submit" onClick={this.onSubmit}>submit-btn<button/>
          </FormContext.Provider>
        );
      }
    }

    ReactDOM.render(
        <CompA />,
        document.getElementById('root'),
      );
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>React App</title>
</head>

<body>
  <div id="root"></div>
  
</body>

</html>

But it does looks verbose to have form separated from submit button. Not sure why you have such a need but normally it's better to keep them together in one component.

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

2 Comments

thank you for the detailed answer, I have edited my post to include code snippets, could you please have a look
Hi, I've read your updated snippets. You can choose to use React Context as I shown in the code example of my answer, if there are <CompB>, <CompC> or even more layers between <CompA> and <CompB>; or if there is not that much layers, you can just pass onSubmit, onChange, etc. from <CompA> down to <CompD> like the other answer shown.
1

React Components use props to communicate with each other. Parent -> Child goes via props, Child -> Parent goes via callback props. Another way is to use new Context API. It would be better if you have a really deep nested structure.

Bellow I shown how it could be done via props.

class CompB extends React.Component {
  state = {
    name: "John Doe",
    email: "[email protected]"
  }
  
  handleChange = e => {
    const {name, value} = e.target;       
    this.setState({[name]: value})
  }
  
  handleSubmit = e => {
    e.preventDefault();
   
    this.submit(this.state)
  }
  
  submit = (data) => {
    console.log("submitted", data, +new Date());
    
    this.props.onSubmit(data);
  }
  
  componentDidUpdate (oldProps) {
    if (!oldProps.shouldSubmit && this.props.shouldSubmit){
      this.submit(this.state);
    }
  }
  
  render () {
    const { name, email } = this.state; 
 
    return (
      <form onChange={this.handleChange} onSubmit={this.handleSubmit}>
        <div>
          <label>
            Name
            <input name="name" type="text" value={name} />
          </label>
        </div>

        <div>
          <label>
            Email
            <input name="email" type="email" value={email} />
          </label>
        </div>
      </form>
    )    
  }
}

class CompA extends React.Component { 
  state = {
    shouldSubmit: false,
  }
  
  submit = () => {
    this.setState({shouldSubmit: true})
  }
  
  handleSubmit = () => {
    this.setState({shouldSubmit: false})
  }
  
  render () {
    const {shouldSubmit} = this.state;
    
    return (
      <div>      
        <CompB 
          shouldSubmit={shouldSubmit} 
          onSubmit={this.handleSubmit}
        />
        
        <button 
          type="button" 
          onClick={this.submit}
        >
          Submit
        </button>
      </div>
    )
  }
}


ReactDOM.render(<CompA />, document.querySelector("#root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

4 Comments

thank you for the detailed answer, I have edited my post to include code snippets, could you please have a look
Do you want to keep data storing in <CompD />?
I'm not sure, but I want to keep the Form there, so does that mean the data should be kept there too?
Thanks will check, if it makes a difference, I am using a Redux form and map state to props

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.