DEV Community

loading...
Cover image for Passing Data from Child to Parent with React Hooks

Passing Data from Child to Parent with React Hooks

pnkfluffy profile image Jackson Felty ・4 min read

I was recently challenged to make a simple working implementation of a react authentication system using hooks. I was linked this article as an example, and found it a pretty interesting way to have a single line controlling the authentication for the entire app. For the demo, I wanted the user to be able to type in a username to 'log in', then have the website display 'hello, [username]' to greet the user.

The General Layout

The general idea behind this demo is to have a single state in the root parent component which holds the user authentication. Based on whether or not the user is authenticated, a different version of the website loads.

const App = () => {
  const [user, setUser] = useState(null);

  return user ? <AuthWebsite/> : <NoAuthWebsite/>
};

simple, right? But how does does the state get updated? There must be a way to pass user information up the component tree so that can update the information stored in the [user] state.

Passing Data from Child to Parent

Wait, isn't unidirectional data flow a core design philosophy of react? Well, that is true. And we shouldn't be passing anything up the component tree using the most common method for passing data around, props. However, we can actually design functions in our parent, and pass them down the component tree. It is possible to send variables or any other data back up as an argument that you pass into the function in the child.

A child component passing a username back up the component tree looks like this:

const NoAuthWebsite = ({ login }) => {
  const [userName, setUserName] = useState("");

  return (
    <form onSubmit={() => login(userName)}>
      <input
        placeholder="username"
        required="required"
        onChange={e => setUserName(e.target.value)}
        value={userName}
      />
      <button type="submit">
        submit
      </button>
    </form>
  );
};

(the state here is just for storing the user response in the form)

So above, login is taken as a prop in the NoAuthWebsite component. When the user loads the website, this component presents the user with a form to fill out a username. This is submitted as an argument to the login function that was passed down as a prop. Now lets add login() as a function in the parent component that we wrote above, and pass it down:

const App = () => {
  const [user, setUser] = useState(null);

  return user ? (
    <AuthWebsite logout={() => setUser(null)} user={user} />
  ) : (
    <NoAuthWebsite login={username => setUser(username)} />
  );
};

So now we have a username submitted by the user being set in the [user] state. And if that exists, we load the authorized version of the website. And if you noticed, we passed a logout function into our AuthWebsite component so that the user can logout and the website can return to it's default (unauthorized) state. We don't need to pass children up our component tree in this case though, as it just needs to setUser to null. We can build up our authorized website component now, and enable it to welcome the user with their username:

const AuthWebsite = ({ logout, user }) => {
  return (
    <div>
      <h2>Hello, {user}</h2>
      <div className="logout_button" onClick={() => logout()}>
        logout
      </div>
    </div>
  );
};

And there we have it! A simple webapp authentication demo that passes data from child to parent component via functions!

Adding to our Application with a More Interesting Example

The login form that greets our user is a little boring. Lets spice it up while reapplying these same concepts to make a Modal, or a pop up overlay card that the user can either submit or click out of. These modal windows are found all over the web and can be used for almost anything.

Achieving this effect can be done fairly simply, by using a ternary to toggle CSS. With CSS, you can control weather an html element appears using the 'display' property. Similarly to what was done in the first example, a binary state can control the className of the component. Then, a function that toggles the state can be passed into the overlay component itself.

const NoAuthWebsite = () => {
  const [overlay, setOverlay] = useState(false);

  return (
    <div className="flex_column">
      <div className={overlay ? "overlay_shown" : "overlay_hidden"}>
        <LoginOverlay
          removeOverlay={() => setOverlay(false)}
        />
      </div>
      <h2>You are not Authorized</h2>
      <div className="login_button" onClick={() => setOverlay(true)}>
        Login
      </div>
    </div>
  )
}
.overlay_shown {
  opacity: 1;
}

.overlay_hidden {
  display: none;
  opacity: 0;
}

stopPropagation() is used to stop the onClick function on the overlay_background div from propagating to all its children. Without it, clicking anywhere on the modal would cause the onClick to trigger, and remove the modal.

const stopProp = e => {
  e.stopPropagation()
}

const LoginOverlay = ({ removeOverlay }) => {
  const [userName, setUserName] = useState("")

  return (
    <div className="overlay_background" onClick={e => removeOverlay()}>
      <div className="overlay_card" onClick={()) => stopProp(e)}>
        <form onSubmit={e => removeOverlay()}>
          <input
            placeholder="username"
            required="required"
            onChange={e => setUserName(e.target.value)}
            value={userName}
          />
          <button className="form_submit" type="submit">
            submit
          </button>
        </form>
      </div>
    </div>
  )
}

And that's it! After connecting those together and adding a little visual display to see the data flow path, you can see the complete live demo here, or the source code here.

Conclusion

Using functions is a great way to pass data up component trees. It can be used in many ways, most notably rendering based off of user interactions / inputs made in child components. Using this trick with react hooks helps in writing beautiful, maintainable code, as it is easy to follow the flow of logic through functional components and functions themselves.

If you have any questions, comments, issues, or just wanna chat, feel free to shoot me a message.

Discussion

pic
Editor guide
Collapse
peerreynders profile image
peerreynders

The article seems to use a state hooks implementation of the general Lifting State Up pattern to share state with a nested component (if so, a reference/link would be nice - mainly because mentioning "Lifting State Up" gives the approach a name - long before it can reveal itself through the details).

Side note:
The established term "child component" as it is used in this article is at best ambiguous, at worse misleading.

A parent component has its children passed via props.children - so a child component is the ReactNode (or an item in ReactNode[]) in props.children. The parent doesn't create its children but is composed with them.

The React documentation once used to contain the following:

Ownership:

It's important to draw a distinction between the owner-ownee relationship and the parent-child relationship. The owner-ownee relationship is specific to React, while the parent-child relationship is simply the one you know and love from the DOM.

So given:

function Avatar({username}) {
  return (
    <div>
      <ProfilePic username={username} />
      <ProfileLink username={username} />
    </div>
  );
}

function ProfilePic({username}) {
  return (<img src={'https://graph.facebook.com/' + username + '/picture'} />);
}

function ProfileLink({username}) {
  return (
    <a href={'https://www.facebook.com/' + username } >
      {username}
    </a>
    );
}
Enter fullscreen mode Exit fullscreen mode
  • Avatar is the owner (that sets the props) of ProfilePic and ProfileLink.
  • The <div> is the parent (but not the owner) of ProfilePic and ProfileLink.
  • Both ProfilePic and ProfileLink are children to <div> but are ownees of Avatar.

Using that terminology: "Passing Data from Ownee to Owner with React Hooks".

To use this with props.children the parent would likely have to use cloneElement to inject its update function into a component prop to create instances that interact with its state (though it probably makes more sense to use a render prop at that point).

I can only guess why "owner-ownee" was removed from the documentation - maybe they just gave up as everybody keeps using "parent-child" for the "owner-ownee" relationship.

Maybe the term "ownee" was a problem.

The Svelte documentation refers to components that are referenced in the markup section as nested components (child components go into the parent component's <slot></slot>).

Collapse
pnkfluffy profile image
Jackson Felty Author

Hello, and thanks for the reply! I wasn't aware of the owner/ownee terminology as it seems like I've read other people misusing parent/child before as well. You mention that the React documentation used to contain that distinction. Do you think it was removed because the terminology became so misused that parent/child functionally became used to mean both definitions?

Collapse
peerreynders profile image
peerreynders

From Wrong context warning? - 'Warning: owner-based and parent-based contexts differ' #3451

Is a component A a child of component B only if it A is passed as the children property to B?

AFAIK, yes.

According to Documentation inconsistencies around Parent/Child vs Owner/Ownee #7794

I think the biggest issue here is that owner/ownee almost never matters, so it is awkward to mention in the introductory text.

This is a curious statement given how often parent/child is being used in introductory tutorials to describe the relationship between a component and the components being used in its render method - most often attempting to state something like:

"Passing props is how information flows in React apps, from owners to ownees."

So it would seem that the official documentation simply tries to avoid the parent/child terminology unless it's referring to a relationship within the render result (i.e. the DOM tree or some intermediate representation thereof).

React.createElement

React.createElement(
  type,
  [props],
  [...children]
)
Enter fullscreen mode Exit fullscreen mode

So here it should be clear that type is the parent "node" to children - but notice that type (the parent) doesn't create children.

However community contributions to the documentation may re-introduce the notion of the "creational parent":

"Passing props is how information flows in React apps, from parents to children."

In the absence of definitive terminology (i.e. avoiding owner/ownee) it's easy to think of the component that creates another component (while rendering) as the "parent".

The "component tree" could be seen as a hierarchy that represents the rendering process but that is different from the result of the rendering process - the "tree of rendered elements".

Component render vs Document tree

It is possible to imagine the "tree of rendered elements" and draw boxes around the fragments that are rendered by specific components - at that point a component could be perceived as "containing" elements or nested components.

It could also be argued that the "parent-child relationship between nodes" isn't all that interesting in React due to the limitations of the children prop. While children can be used to pass slotted content to a parent component, children needs to be a "children as a function" for the more interesting cases. But in those cases best practice suggests to use render props/component injection - which lessens the importance of children (and by extension the parent-child relationship).

import { createElement as h, render, Component } from 'https://unpkg.com/preact?module';

// 1. In order to emulate a 'scoped slot'
//    the `children` prop has to be a
//    render prop so that the `MyList`
//    component can pass props to its `children`.
//
function MyList({ title, items, children }) {
  return (
    h('section', {class: 'my-list'},
      h('h1', null, title),
      h('ul', null,
        ...items.map(item =>
          h('li', {class: 'my-list__item'},
            children(item) // (1.)
          ))
      )
    )
  );
}

// Given that the slot content
// needs to receive props
// the content needs to be
// implemented as a component
// (`Shape` and `Color`)

function Shape({ shape }) {
  return (
    h('div', null,
      `${shape.name} `,
      h('small', null, `(${shape.sides} sides)`)
    )
  );
}

function Color({ color }){
  return (
    h('div', null,
      h('div', {class: 'swatch', style: `background: ${color.hex}`}),
      color.name
    )
  );
}

// 2. Here `MyList` is parent to `Shape`
//    but as `MyList` needs pass props to `Shape`
//    `children` to `MyList` needs to be function.
// 3. Similarly the `Color``children` to `MyList`
//    has to be a function.
//
function App({ data }) {
  return [
    h(MyList, {title: 'Shapes', items: data.shapes },
      shape => h(Shape, { shape }) // (2.)
    ),
    h(MyList, {title: 'Colors', items: data.colors },
      color => h(Color, { color }) // (3.)
    ),
  ];
}

const data = {
  shapes: [
    { name: 'Square', sides: 4 },
    { name: 'Hexagon', sides: 6 },
    { name: 'Triangle', sides: 3 },
  ],
  colors: [
    { name: 'Yellow', hex: '#F4D03F' },
    { name: 'Green', hex: '#229954' },
    { name: 'Purple', hex: '#9B59B6' },
  ],
};

render(
  h(App, { data }),
  document.getElementById('app')
);
Enter fullscreen mode Exit fullscreen mode

Full gist

Note that in the above example MyList is the parent of Shape (and Color) within the App component. But both Shape and Color have to be passed as "children as a function" (rather than simple child components) so that MyList can pass props to them. However typically this would be implemented with component injection - so the injected component is a simple prop rather than a child.

// 1. Here the 'scoped slot' is emulated with component injection:
//    - the `render` prop references the component to use
//    - the `name` prop identifies the prop name the
//      item should be passed by.
//
function MyList({ title, items, name, render: View }) {
  return (
    h('section', {class: 'my-list'},
      h('h1', null, title),
      h('ul', null,
        ...items.map(item =>
          h('li', {class: 'my-list__item'},
            h(View, { [name]: item }) // (1.)
          ))
      )
    )
  );
}

// ...

// The `App` component using component injection instead
//
// 2. The `Shape` component is injected for rendering
//    while the `item` should be passed via the `shape` prop.
// 3. The `Color` component is injected for rendering
//    while the `item` should be passed via the `color` prop.
//
// In effect the "slot content" is no longer passed as "children"
//
function App({ data }) {
  return [
    h(MyList, {title: 'Shapes', items: data.shapes, name: 'shape', render: Shape}), // (2.)
    h(MyList, {title: 'Colors', items: data.colors, name: 'color', render: Color})  // (3.)
  ];
}
Enter fullscreen mode Exit fullscreen mode

A "parent" is simply an element (or component) with an opening and closing tag (in JSX) and any elements (or components) between those tags are the "children" to that parent. The only time that relationship is really of any interest is with components with a slot:

  • the component with the slot is the "parent"
  • the content placed into the slot is the "child" (or children).

Using parent/child to also refer to the creational relationships between components can make discussions ambiguous and downright difficult when it is necessary to simultaneously discuss relationships within the creational (i.e. rendering) process and within the content model (e.g. DOM tree and the like).


Edit:

Another way to look at it - given:

function App() {
  const colorA = 'green';
  const colorB = 'yellow';
  const colorC = 'blue';
  const colorD = 'orange';

  return (
    <div>
      <CompA color={colorA}>
        <CompC color={colorC} />
      </CompA>
      <CompB color={colorB}>
        <CompD color={colorD} />
      </CompB>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • CompA is the parent to CompC
  • CompB is the parent to CompD
  • The props are being passed from App to CompA, CompB, CompC and CompD.

How does the statement:

"Passing props is how information flows in React apps, from parents to children."

hold up now?

  • No props are being passed from CompA to CompC.
  • No props are being passed from CompB to CompD.
  • All props come from App.

I guess:

  • CompA and CompB are parents from the content/document perspective.
  • App is a parent from the rendering perspective.

But nobody seems to bother with that distinction.

Collapse
psaillesh profile image
Saillesh pawar

When I run the code pen and enter the username it always says You are not Authorized.

Collapse
pnkfluffy profile image
Jackson Felty Author

Yes, the codepen is just to show the functionality of the modal. I have the full workimg demo in the link at the end

Collapse
psaillesh profile image
Saillesh pawar

Thanks for replying, will check again.

Thread Thread
pnkfluffy profile image
Jackson Felty Author

I can see how that would be confusing. I changed the pen for clarity!

Collapse
tocvfan profile image
Christian Hammervig

I can't get it to work I get a login is not a function

Collapse
pnkfluffy profile image
Jackson Felty Author

Hi Christian, have you taken a look at the source code?
github.com/pnkfluffy/react_auth_demo

If you are still having difficulty, feel free to share your code here

Collapse
georgewl profile image
George W Langham

This may be easier with the new context API I feel.

Still a really cool example.

Collapse
pnkfluffy profile image
Jackson Felty Author

Yes! Context API is certainly a better way to do this if your passing data through multiple layers of the component tree. I feel like simply using a function is a better, more readable method when one variable needs to be passed up a single component.

Collapse
psaillesh profile image
Saillesh pawar

Thanks for sharing the valuable information.

Collapse
dewaleolaoye profile image
Adewale Olaoye

Thanks for sharing, this is really nice.
I follow up with the article and write it in Typescript, you can view it here
github.com/dewaleolaoye/auth