DEV Community

Cover image for Understanding Inverse Data Flow in React 💃đŸģ
isabel k.
isabel k.

Posted on

14 5

Understanding Inverse Data Flow in React 💃đŸģ

What is inverse data flow?

In React, inverse data flow allows us to send data between parent and child components as props, or properties. However, components that are cousins or siblings cannot directly communicate with each other.

Sharing data between parent and child components

Here's an example of inverse data flow between a parent component and a child component. Let's say we're building an app that allows users to create accounts by entering their email addresses.



class Home extends React.Component {
  state = {
    email_address: ""
  }

  handleChange = (inputFromChild) => {
    this.setState({
      email_address: inputFromChild
    })
  }

  handleResponse = (event) => {
    event.preventDefault()
    console.log("Something has been done.")
  }

  render () {
    return (
      <CreateAccountForm
        handleChange={this.handleChange}
        handleResponse={this.handleResponse}/>

      <AccountSettings
        handleChange={this.handleChange}
        handleResponse={this.handleResponse}/>
    )
  }
}


Enter fullscreen mode Exit fullscreen mode

In our Home component, we're defining the handleChange() and handleResponse() functions and then sending them down as props to its child components, CreateAccountForm and AccountSettings. The information inputted by the user in these child components is then sent back up to the parent component by invoking those very same functions. This allows us to "reuse" these functions without having to copy and paste the same code in both of the child components.

If we didn't use props, here's what our components might look like:



class Home extends React.Component {
  state = {
    email_address: ""
  }

  render () {
    return (
      <CreateAccountForm />
      <AccountSettings />
    )
  }
}

class CreateAccountForm extends React.Component {
  handleChange = (inputFromChild) => {
    this.setState({
      email_address: inputFromChild
    })
  }

  handleResponse = (event) => {
    event.preventDefault()
    console.log("Something has been done.")
  }

  render () {
    return (
      <div>
        <form onSubmit={this.handleResponse}>
          <label>Email Address: </label>
          <input type="text" name="email_address" onChange={this.handleChange} />
          <input type="submit" value="Create Account" />
        </form>

      </div>
    )
  }
}

class AccountSettings extends React.Component {
  handleChange = (inputFromChild) => {
    this.setState({
      email_address: inputFromChild
    })
  }

  handleResponse = (event) => {
    event.preventDefault()
    console.log("Something has been done.")
  }

  render () {
    return (
      <div>
        <form onSubmit={this.handleResponse}>
          <label>Email Address: </label>
          <input type="text" name="email_address" onChange={this.handleChange} />
          <input type="submit" value="Create Account" />
        </form>

      </div>
    )
  }
}


Enter fullscreen mode Exit fullscreen mode

This isn't very DRY, is it? It also makes things complicated if we want to update the handleChange() and handleReponse() functions in both places. Placing those two functions in the Home component and sending it down to its child components creates a single source of truth.

Limitations of inverse data flow

While inverse data flow is great for writing DRYer code, it can sometimes be too restrictive. For example, components that do not have a direct parent or child relationship cannot share props with each other.

Graphic of inverse data flow relationships

If we wrote a function called toggleFormVisibility() in our CreateAccountForm component, and we wanted to use it in our AccountSettings component, it would be not be available as a prop. In order to create access to that function, we would have to send it back up to the parent and back down to AccountSettings.



class CreateAccountForm extends React.Component {
  state = {
    displayForm: false
  }

  toggleFormVisibility = () => {
    this.setState({
      displayForm: !this.state.displayform
    })
  }

  render () {
    return (
      <div>
        <button onClick={this.toggleFormVisibility}>Show the form</button>

        <form onSubmit={this.props.handleResponse}>
          <label>Email Address: </label>
          <input type="text" name="email_address" onChange={this.props.handleChange} />
          <input type="submit" value="Create Account" />
        </form>

      </div>
    )
  }
}


Enter fullscreen mode Exit fullscreen mode

This process of sharing data can become quite cumbersome and confusing to follow if there are several components with complex relationships.

Summary

1) Define the function in the parent component.



class Home extends React.Component {
  state = {
    email_address: ""
  }

  handleChange = (inputFromChild) => {
    this.setState({
      email_address: inputFromChild
    })
  }

  render () {
    return (
      <CreateAccountForm />
      <AccountSettings />
    )
  }
}


Enter fullscreen mode Exit fullscreen mode

2) Send down the function as props to the child component.



class Home extends React.Component {
  ...

  render () {
    return (
      <CreateAccountForm handleChange={this.handleChange} />
      <AccountSettings handleChange={this.handleChange} />
    )
  }
}


Enter fullscreen mode Exit fullscreen mode

3) Invoke the function in the child.
4) Send data back up to the parent as props.



class CreateAccountForm extends React.Component {
  render () {
    return (
      <div>
        <form>
          <label>Email Address: </label>
          <input type="text" name="email_address" onChange={this.props.handleChange} />
          <input type="submit" value="Create Account" />
        </form>

      </div>
    )
  }
}


Enter fullscreen mode Exit fullscreen mode

5) Voila! You've just created inverse data flow.

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • --last-failed: Zero in on just the tests that failed in your previous run
  • --only-changed: Test only the spec files you've modified in git
  • --repeat-each: Run tests multiple times to catch flaky behavior before it reaches production
  • --forbid-only: Prevent accidental test.only commits from breaking your CI pipeline
  • --ui --headed --workers 1: Debug visually with browser windows and sequential test execution

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Practical examples included!

Watch Video 📹ī¸

Top comments (1)

Collapse
 
mjoycemilburn profile image
MartinJ â€ĸ

Well done - the template in your "summary" could very usefully be incorporated in React's own "reactjs.org/docs/thinking-in-react... document. This just describes the problem in the abstract and will leave a lot of learners completely cold

nextjs tutorial video

Youtube Tutorial Series đŸ“ē

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

If this article connected with you, consider tapping ❤ī¸ or leaving a brief comment to share your thoughts!

Okay