React: Call Child Functions from Parent Functional Component

In the dynamic world of React development, one question often leads to another, unfolding layers of complexity and elegance in code. Recently, a junior programmer on my team approached me with a seemingly straightforward query: “How can I call child functions from a parent functional component in React?” Another colleague quickly interjected, “You can’t do that directly; you should use state management.” This exchange led to a fascinating discussion and a deeper dive into React’s capabilities.

To shed light on this, I referred them to my previous article on integrating React with SignalR, an advanced topic intertwining state management and real-time data flow. After a few hours of diligent study, they returned, impressed yet slightly perplexed. “Your approach in the connector is ingenious,” they said, “but it’s not entirely clear how to replicate this concept, especially when it comes to passing functions as props. We understand what you did there with connector class, but how to emit events from parent to child … we just can’t get it to work”

This conversation resonated with me. If my team members, who read my post on this intricate subject, still had questions, it meant others might too. Therefore, I decided to pen this article, aiming to elucidate the “Controlled Component with Event-Driven Callbacks” pattern in React—a technique that, while not immediately obvious, unlocks a new realm of possibilities in component interaction.

In this blog, I aim to bridge this gap in understanding. By the end of this read, not only will my junior colleagues grasp this concept fully, but so will many others who find themselves puzzled over this aspect of React. Let’s unravel the mystery of “React: Call Child Function from Parent Functional Component,” ensuring that this vital piece of knowledge becomes second nature to those who tread the path of React development.

Call Child Function from Parent Functional Component

Scenario

In this simple React example, we’ll demonstrate how a parent component can call a function in a child component. The child component contains a button; when clicked, it triggers a function that sends a message to the parent. The parent, upon receiving this message, processes it (makes it upper case!) and then passes it back to the child component. This round-trip communication is managed using a combination of props and callbacks. The result is a text box in the child component that gets updated based on the user’s interaction with the button, showcasing a practical instance of parent-child communication in React.

React Implementation

We have two components: Chat (child) and Parent. The Chat Component includes an input field for users to type messages and a button to send them. When the send button is clicked, it calls the onSend function passed from the ParentComponent, but it doesn’t update its state directly for the sent message. Instead, it relies on the onReceiveMessage callback to update its state when new messages arrive.

The Parent Component serves as the central manager of the messages. It provides the onReceiveMessage function to the ChatComponent, allowing for dynamic updating of the message list within the child component. This function gets triggered both when the ParentComponent receives messages from an external source and when the user sends a message from the Chat Component. This way, all messages, whether incoming or outgoing, are managed consistently, showcasing an efficient and elegant method of parent-child communication in React.

Child Component

Chat.js:

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

const ChatComponent = ({ onSend, onReceiveMessage }) => {
    const [inputText, setInputText] = useState('');
    const [messages, setMessages] = useState([]);

    useEffect(() => {
        onReceiveMessage((newMessage) => {
            setMessages(prevMessages => [...prevMessages, newMessage]);
        });
    }, [onReceiveMessage]);

    const handleSend = () => {
        onSend(inputText);
        setInputText('');
    };

    return (
        <div>
            <ul>
                {messages.map((msg, index) => (
                    <li key={index}>{msg}</li>
                ))}
            </ul>
            <input 
                type="text"
                value={inputText}
                onChange={e => setInputText(e.target.value)}
                placeholder="Type a message"
            />
            <button onClick={handleSend}>Send</button>
        </div>
    );
};

export default ChatComponent;

Lines 7-11 (useEffect Hook): This is a useEffect hook that sets up a listener for incoming messages. The onReceiveMessage function, passed from the parent component, is called with a callback function as its argument. This callback adds a new message to the messages state. The useEffect hook’s dependency array includes onReceiveMessage, ensuring that if the onReceiveMessage function changes, the effect will re-run and update the listener accordingly.

Line 14 (handleSend Function): This is the handleSend function triggered when the send button is clicked. It calls the onSend function, passing the current value of inputText (the message to be sent). After sending the message, it clears the input field by setting inputText to an empty string using setInputText('').

Line 31 (Send Button): This line defines a button in the component’s rendered output. The onClick event handler is set to the handleSend function, which means that when the button is clicked, it triggers the handleSend function. As previously described, the handleSend function calls the onSend prop (a function provided by the parent component) with the current input text, and then clears the input field. This button is the interactive element that users click to send their messages.

Parent Component
Parrent.js:

import React, { useCallback } from 'react';
import ChatComponent from './ChatComponent';

const ParentComponent = () => {
    let receiveMessage;

    const handleMessageSend = (message) => {
        console.log("Message sent from chat:", message);
        if (receiveMessage) {
            receiveMessage( message.toUpperCase() );
        }
    };

     const setReceiveMessage = useCallback((callback) => {
        receiveMessage = callback;
    }, []); // dependencies array is empty, meaning this function is created once

    // Additional logic for handling external message sources...

    return (
        <div>
            <ChatComponent 
                onSend={handleMessageSend} 
                onReceiveMessage={setReceiveMessage} 
            />
            {/* Other components... */}
        </div>
    );
};

export default ParentComponent;

Lines 7-12 (handleMessageSend Function): This function, handleMessageSend, is called when a message is sent from the Chat Component. First, it logs the message to the console. Then, it checks if receiveMessage (a function to handle incoming messages in the child component) is defined. If it is, handleMessageSend calls receiveMessage, passing the sent message in uppercase. This effectively sends the message back to the child component, but transformed to uppercase.

Lines 14-16 (setReceiveMessage Function): Here, setReceiveMessage is a function that assigns the provided callback to receiveMessage. This callback is the function from the child component (Chat Component) that updates its state with new messages. This function essentially sets up the mechanism for the parent to communicate incoming messages to the child. It is a callback functionality that is important so this also would work (just to help you understand what we are doing here):

const setReceiveMessage = (callback) => {
        receiveMessage = callback;
    };

In React, useCallback returns a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.

setReceiveMessage is wrapped with useCallback with an empty dependency array. This means setReceiveMessage will maintain a stable reference across renders unless the component is unmounted and remounted.

By going for useCallback, you ensure that the onReceiveMessage prop in ChatComponent doesn’t cause unnecessary effect re-runs, as it won’t be seen as a changed prop unless ParentComponent is re-rendered for some reason. This pattern is particularly useful in performance-sensitive applications where preventing unnecessary renders and effect executions is important.

Lines 23-24 (Rendering ChatComponent): In these lines, the Parent Component renders the ChatComponent. It passes handleMessageSend as the onSend prop and setReceiveMessage as the onReceiveMessage prop. This setup allows the ChatComponent to communicate with the ParentComponent both ways: sending out messages (via onSend) and receiving messages (via the mechanism set up in setReceiveMessage).

Pattern: Controlled Component with Event-Driven Callbacks

The pattern demonstrated in this example is a variation of the “Controlled Component” pattern in React, adapted with an additional event-driven approach. It’s designed to solve specific problems in component communication and state management. Let’s break down the aspects of this pattern:

  1. Controlled Component: In React, a “Controlled Component” is one where the state is managed by a parent component rather than by the component itself. The child component receives its state via props and notifies changes to the parent through callbacks. This is a common pattern for form elements like text inputs, but it can be extended to more complex components like your chat interface.
  2. Event-Driven Callbacks: The use of callbacks to handle events (like receiving a message) is an event-driven approach. In this pattern, the child component (ChatComponent) doesn’t manage the state of the messages directly. Instead, it notifies the parent component (ParentComponent) of the need to update the state (when a message is sent) and relies on a callback function (onReceiveMessage) to update its own state when new messages arrive.

Problems Solved by This Pattern

  1. Separation of Concerns: The chat component doesn’t need to know where the messages are coming from or how they’re being processed. It focuses solely on displaying messages and gathering user input. This makes the component more reusable and easier to test.
  2. Centralized State Management: The state of the messages is managed by the parent component, allowing for easier state synchronization across different components and avoiding issues with inconsistent states.
  3. Flexibility and Scalability: This pattern makes it easier to extend the chat component’s functionality. For instance, if you decide to fetch messages from a server or implement more complex messaging features, these changes can be managed in the parent component without major changes to the chat component itself.
  4. Event-Driven Communication: By using callbacks for incoming messages, the component’s reaction to new data is handled more dynamically, resembling an event-driven architecture. This is particularly useful in scenarios where the data source is not just a simple state change (e.g., real-time messaging).

This pattern is particularly useful in complex applications where multiple components need to interact with shared state in a predictable manner. It enhances maintainability and clarity, especially as the application grows and evolves.

Try It Yourself: Code Available on GitHub

To help you fully grasp the concepts we’ve discussed, I’ve made the complete code for this example available on GitHub. You can explore, clone, or even experiment with the code online using GitHub Codespaces. This practical approach will provide you with hands-on experience and a deeper understanding of how parent-child communication works in React.

Access the Code:

Feel free to clone the repository, make your modifications, or even use it as a starting point for your projects. If you’re new to GitHub Codespaces, it offers an incredible opportunity to work with the code in a fully configured development environment, right in your browser.

By experimenting with this code, you can see how the message flow works in action, observe the effects of changes, and really solidify your understanding of the patterns we’ve explored. If you have any questions, thoughts, or feedback, don’t hesitate to open an issue or a discussion in the repository. Happy coding, and I look forward to seeing how you apply these concepts in your React applications!


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *