In React, the general rule is: data flows in one direction – down. Without getting into data sharing processes like Context or Redux, generally speaking, in React, data flows down from parents to children. That is one of the things that makes React so efficient.

However, data can in effect be “passed up” to a Parent using a callback function defined in the Parent. That callback is passed down to the Child as a prop using a custom “on” event key name, like onDoCallback.

For example, a callback function called doCallback() defined in the Parent can be passed down to the Child as a prop like this:
    <Child onDoCallback={doCallback} />

The Child component function then takes in the custom “on” event name { onDoCallback } as a prop, and uses it to execute the callback function defined in the Parent. So in the Child, calling onDoCallback(data) is in effect, executing the function doCallback(data) in the Parent. The (data) parameter is provided to the Parent function from the Child, so you could say, data is being passed up to the Parent.

It can look something like this:

Parent.js

const [parentData, setParentData]=useState("")
function Parent() {
  function doCallback(data) {
    setParentData(data)
  }
  return (
    <div >
      <Child onDoCallback={doCallback} />
    </div>
  )
}
export default Parent;

Child.js

function Child({ onDoCallback }) {
const [formData, setFormData]=useState("")
function handleSubmit(event) {
  event.preventDefault()
    const newData={
      formData
    }
    onDoCallback(newData)
  }
  return (
    <div>
      <form onSubmit={handleSubmit}>
      <input type="text" id="formData"
      value={formData}
      onChange={e => setFormData(e.target.value)}
      />
      <button type="submit">Add Data</button>
    </form>
    </div>
  )
}
export default Child;

When the form is submitted in the Child, the Child’s newData is passed to the doCallback function in the Parent, which then can be used to update state.

 

But what about passing data from a Child to a Grandparent (or GreatGrandparent)?

The process is similar, but slightly different.

In this case, like the previous example, the doCallback() function is defined in the top component, let’s say the Grandparent. The Grandparent passes the doCallback() to it’s child (Parent) as a prop using a custom “on” event keyname, much like the previous example:

    <Parent onDoCallback={doCallback} />

The Parent component function takes { onDoCallback } as a prop, and simply passes it to the child component. But notice there is one subtle difference in the way it is passed. Instead of using onDoCallback={doCallback}, the Parent uses onDoCallback={onDoCallback} The key and the value are the same value.

    <Child onDoCallback={onDoCallback} />

So it would look something like this:

GrandParent.js

const [grandParentData, setGrandParentData]=useState("")
function GrandParent() {
  function doCallback(data) {
    setGrandParentData(data)
  }
  return (
    <div >
      <Parent onDoCallback={doCallback} />
    </div>
  )
}
export default Parent;

Parent.js

function Parent({ onDoCallback }) {
  // other Parent code
  return (
    <div >
      <Child onDoCallback={onDoCallback} />
    </div>
  )
}
export default Parent;

Child.js
function Child({ onDoCallback }) {
const [formData, setFormData]=useState("")
function handleSubmit(event) {
  event.preventDefault()
  const newData={
    formData
  }
  onDoCallback(newData)
}
return (
  <div>
    <form onSubmit={handleSubmit}>
      <input type="text" id="formData"
      value={formData}
      onChange={e => setFormData(e.target.value)}
      />
      <button type="submit">Add Data</button>
    </form>
  </div>
)
}
export default Child;

—————————-

Now, when the Child fires off onDoCallback(newData)
it is, in effect, passing data from Child to a function in the GrandParent.