React Component Communication: Parent-Child and Child-Parent Interactions

sandheep_kumarpatro_1c48

Sandheep Kumar Patro

Posted on November 23, 2024

React Component Communication: Parent-Child and Child-Parent Interactions

1. Introduction

In React applications, parent and child components often need to communicate. For instance, a parent might send a callback function to a child to handle an event like submitting a form. Conversely, the parent might need to trigger a specific behavior in the child, such as resetting an input field or closing a modal.

While passing functions from parent to child is straightforward, triggering a child function from the parent requires additional steps. In this blog, we’ll cover both patterns with real-world examples and explain their implementation in a meaningful way.


2. Triggering Parent Function from Child Component

Use Case: Form Submission

Imagine you’re building a form where a child component handles user inputs but the final submission logic resides in the parent. The child component triggers the parent’s submission function when the user submits the form.

Example:

// ParentComponent.jsx
import React from 'react';
import FormInput from './FormInput';

function ParentComponent() {
  const handleSubmit = (data) => {
    console.log('Form Submitted with Data:', data);
    alert(`Form submitted with: ${JSON.stringify(data)}`);
  };

  return (
    <div>
      <h1>Submit Form</h1>
      <FormInput onSubmit={handleSubmit} />
    </div>
  );
}

// FormInput.jsx
import React, { useState } from 'react';

function FormInput({ onSubmit }) {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleFormSubmit = () => {
    if (name && email) {
      onSubmit({ name, email });
    } else {
      alert('Please fill in all fields!');
    }
  };

  return (
    <div>
      <label>
        Name:
        <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      </label
        Email:
        <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      </label>
      <br />
      <button onClick={handleFormSubmit}>Submit</button>
    </div>
  );
}

export default FormInput;

Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The ParentComponent holds the form submission logic in the handleSubmit function.
  • The FormInput component captures user input and triggers the parent’s onSubmit function when the user clicks the "Submit" button.
  • This pattern ensures that the parent has full control over what happens after the form is submitted, while the child handles user interactions.

3. Triggering Child Function from Parent Component

Use Case: Resetting a Form or Modal

Now let’s consider a scenario where the parent needs to reset a form in the child component. For example, after successfully submitting the form, the parent might want to clear all input fields.

Example:

// ParentComponent.jsx
import React, { useRef } from 'react';
import FormInput from './FormInput';

function ParentComponent() {
  const formRef = useRef();

  const handleFormSubmit = (data) => {
    console.log('Form Submitted with Data:', data);
    alert(`Form submitted with: ${JSON.stringify(data)}`);
    formRef.current.resetForm(); // Trigger the reset function in the child
  };

  return (
    <div>
      <h1>Form with Reset</h1>
      <FormInput ref={formRef} onSubmit={handleFormSubmit} />
    </div>
  );
}

// FormInput.jsx
import React, { useState, forwardRef, useImperativeHandle } from 'react';

const FormInput = forwardRef(({ onSubmit }, ref) => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleFormSubmit = () => {
    if (name && email) {
      onSubmit({ name, email });
    } else {
      alert('Please fill in all fields!');
    }
  };

  const resetForm = () => {
    setName('');
    setEmail('');
    alert('Form has been reset!');
  };

  // Exposing the resetForm function to the parent via useImperativeHandle
  useImperativeHandle(ref, () => ({
    resetForm,
  }));

  return (
    <div>
      <label>
        Name:
        <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      </label>
      <br />
      <label>
        Email:
        <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      </label>
      <br />
      <button onClick={handleFormSubmit}>Submit</button>
    </div>
  );
});

export default FormInput;

Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. Using useRef: The ParentComponent creates a ref and passes it to the FormInput child.
  2. useImperativeHandle: The FormInput component exposes its resetForm function to the parent via the useImperativeHandle hook.
  3. Reset Trigger: After submission, the parent calls formRef.current.resetForm() to reset the form fields in the child.

4. Complete Code Example

Here’s a combined example: a parent manages form submission and also triggers a reset of the form from the child after submission.

import React, { useRef, useState } from 'react';

// Parent Component
function ParentComponent() {
  const formRef = useRef();

  const handleFormSubmit = (data) => {
    console.log('Form Submitted:', data);
    alert(`Submitted Data: ${JSON.stringify(data)}`);
    formRef.current.resetForm();
  };

  return (
    <div>
      <h1>Complete Form Example</h1>
      <FormInput ref={formRef} onSubmit={handleFormSubmit} />
    </div>
  );
}

// Child Component
const FormInput = React.forwardRef(({ onSubmit }, ref) => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleFormSubmit = () => {
    if (name && email) {
      onSubmit({ name, email });
    } else {
      alert('Please fill in all fields!');
    }
  };

  const resetForm = () => {
    setName('');
    setEmail('');
    alert('Form has been reset!');
  };

  React.useImperativeHandle(ref, () => ({
    resetForm,
  }));

  return (
    <div>
      <label>
        Name:
        <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      </label>
      <br />
      <label>
        Email:
        <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      </label>
      <br />
      <button onClick={handleFormSubmit}>Submit</button>
    </div>
  );
});

export default ParentComponent;

Enter fullscreen mode Exit fullscreen mode

5. Practical Scenarios and Best Practices

  • When to Use Parent-to-Child:
    • Ideal for delegating actions like handling user interactions (e.g., button clicks) to the parent.
  • When to Use Child-to-Parent:
    • Useful when the parent needs to control the child’s behavior dynamically (e.g., resetting forms or triggering animations).

Best Practices:

  1. Keep Components Simple: Avoid over-complicating communication between parent and child.
  2. State Management: Use state lifting or context for complex state sharing across components.
  3. Minimize Ref Usage: Use refs sparingly to avoid tightly coupling components.

6. Conclusion

React provides robust ways to manage component communication. Using callback props for parent-to-child communication and useRef with useImperativeHandle for child-to-parent interactions empowers developers to handle complex workflows. Apply these techniques thoughtfully to build maintainable and scalable React applications.

💖 💪 🙅 🚩
sandheep_kumarpatro_1c48
Sandheep Kumar Patro

Posted on November 23, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related