⚠️ This lesson is retired and might contain outdated information.

Building a React.js App: Managing State in Child Components

Tyler McGinnis
InstructorTyler McGinnis
Share this video with your friends

Social Share Links

Send Tweet
Published 9 years ago
Updated 6 years ago

In this video, we’ll walk through how to better manage your state by learning how to manipulate the state of a parent component from a child component.

We've talked a lot in this series how the state of these three components is being managed in this outer profiles component. What we're going to talk about in this video is how we can manipulate the state of an outer component from inside of another component. Specifically, this addNote component is what we're going to build.

Let's go ahead and head over to our profile.js file. The biggest thing to remember is that you want to manipulate the state where that state lives. For example, this state of this notes is living in the profiles component, so what we want to do is we want a write a function inside of this profiles component, pass that function down to our child component, and then eventually invoke it.

Let's go ahead and write that very first function that we're going to pass down to our child component. We're going to call it handle addNote. What it's going to do is it's going take in a new note, and then we are going to update Firebase with the new note.

What's going to happen is, because we have bound our note's state to our child ref here, whenever we update this endpoint, those changes are automatically going to be pushed through to our state. What's going to happen is we'll update Firebase. Those changes will be pushed through. The component will re-render, and then we'll have that brand new note.

Here, what we're going to do is we're going to say this.ref.child at this.props.params.username, because we don't want to update our root ref. Instead, we want to update the endpoint at our root ref/, whatever the username we're on is. Then we're going to go one level deeper. I'm going to say .child this.state.notes.length.

What's going to happen is we're eventually going to call .set. What .set does is, whatever we pass in, that's going to replace the data at this location. Or, if there's no data there, as in this case, it's going to set that new data.

There's a few ways to do this. Firebase also has a .push method that we can use, but that's going to create its own key. Instead, we want to use the the key 012345, or however many items are in the array. What I'm going to do now is we're going to pass in newNote.

Again, what's going to happen here is when this function's called, it's going to be given a new note, and then we're going to go to this.ref.child/TylerMcGinnis, or / whatever username we're on. Then we're going to go to / however many items are in the array, and then we're going to set a brand-new item there to newNotes.

What that should do is that should just append this new note to the end of our Firebase. What that will do is, because we have, up here, used bindAsArray, that's going to receive new data, and that's gong to push through to our note state, which then updates the view.

Now what we need is we need some way to get this handle addNote down to our notes container so that we can create that button and invoke it there. As you guessed it, what we're going to do is pass it in as a prop. Let's say addNote, and it's going to be equal to this.handle.addNote.

Now let's head over to our notes component. Very first thing, let's go ahead and add a prop type, because this is going to be required. For functions, you do you func, end is required.

As we talked about with the notes list, we could go ahead and make this component a part of the notes component, or what we can do is just make it its own component, which I like a little bit better.

Let's go ahead and create a new file in our notes folder, and let's call it addNote.js. We're going to require react. We're going to create our component, using react.createClass. Then, as always, we're going to export it.

Here, let's go ahead and very first thing now we're going to do some type checking. It's going to take in a username which is a string, and it is required. It's also going to take in that addNote function that we're getting from our profile component. So, PropTypes.func.isRequired.

Now let's go ahead and have a render function. What this is going to return is our UI, which a div. Let's give it a classname of input-group. Now what we want to do is we need an input field, and we also need a button that we can click to submit the new note.

Let's go and make our input field, type=text, classname is form-control, which is a Bootstrap thing. Let's also give it a placeholder of addNewNote.

Now what we want to do is, if you'll remember, we have our handle addNote function. We eventually want to give that a new note. That new note is going to be whatever we type into this input field. What we need is a way to get the value of this input field before we pass it to our addNote function.

What we're going to do is we're going to use this thing called a ref. What a ref is is basically like a name tag that you give an input field, so that you can access that specific value later. The ref I'm going to give it, we're going to give it a function. Let's call that function note setRef.

Inside of this function, we are going to be passed the specific ref, and then we're going to take that ref, and then add it as a property on our instance. Later, we'll be able to query this property, and we'll be able to get the value of this specific input field.

Let's go ahead and make a button. One side note -- this app isn't very accessible. I'm big into accessibility, but for the sake of just learning React, we're going to ignore this for now, which is something you should never do. This button is going to have a type of button, and we are going to have an onClick handler.

Now what's going to happen is we want to run a certain function whenever someone clicks on this button. The function we're going to run, let's do handleSubmit. Then, up here, let's go ahead and make a handleSubmit method that doesn't do anything for now, until we finish off our button. Let's give it Submit, and then we'll close our button.

Now what we want to have happen is, whenever someone clicks on this button, we're going to grab the value from this input field and we're going to pass it to a function that we're eventually going to pass down as a prop.

Now what we want to do is finish off this handleSubmit function. The very first thing that we need to do is we need to go to our input form, and then we need to grab the value off of it and save that. I'm going to save var newNote = this.not.value. Remember, this.note is coming because that is what we did here with setRef, with our ref. Then, .value is a property on our specific ref.

So we have the value, and then what I'm going to do is say this...value equals an empty string, just to clear that input field. Then I'm going to call this.props.addNote, passing it our new note. Remember, addNote, we're getting from our profile component, so when we call this.props.addNote, we're going to that new note, pass it to handle addNote, which should then update Firebase.

Now we need to make sure that addNote is getting passed from our profile component, all the way down to our addNote component. Let's go ahead and check out our notes component. Now let's go ahead and required addNote. Now what we can do is, right here, let's go ahead and throw that in.

AddNote took in two things. It took in a username, which, again, we're getting from props, so this.props.username. It's going to also take in a addNote function, which we're also getting from props. If this is working correctly, the way it works is we have our notes component, and we're passing it the notes.

We're also passing it this handle addNote function, which is right here, which is going to eventually, when it gets invoked, take in a new note, and then set -- whatever profile we're on -- a new value to whatever new note's passed in.

If we follow this, we go to notes. This creates a addNotes component, where we give it a username and we give it addNote.

In addNote, when this button is clicked, whatever's in this input field gets taken, gets reset, and gets passed to the functions. Inevitably, something is wrong with this. Let's go ahead and see if it's working.

So Webpack is working, we refresh, and there we go. It worked.

Kendall
Kendall
~ 9 years ago

Found I had to add ['.value'] to the NotesList render to get just the value back and not the key as well.

So it becomes

{note['.value']}

Marcelo
Marcelo
~ 9 years ago

I am getting an error in the browser console Uncaught Error: Firebase.set failed: First argument contains an invalid key (.key) in property 'jakelingwall.0'. Keys must be non-empty strings and can't contain ".", "#", "$", "/", "[", or "]"

Richard
Richard
~ 9 years ago

I don't know why it is happening but if you change package.json from reactfire ^0.5.0 to ^0.4.0 and then calling npm install... it will fix this problem.

The demo app was built using ^0.4.0 - so it is probably a reasonable fix ... see package.json on https://github.com/tylermcginnis/github-notetaker-egghead

Rich

Junhak
Junhak
~ 9 years ago

https://www.firebase.com/blog/2015-07-15-reactfire-0-5-0.html

Joel Hooks
Joel Hooks
~ 9 years ago

We will update these lessons at some point to reflect the new ReactFire library, but for now, you will want to use v0.4.0

npm install reactfire@0.4.0 --save

Jim Jeffers
Jim Jeffers
~ 9 years ago

To take advantage of the current firebase API you need to add the note like this:

handleAddNote: function(newNote){ this.firebaseRefs['notes'].push({ text: newNote }); }

Also to properly display the contents of a note you'll want to do use the text attribute as the objects in the notes array are objects not strings:

var notes = this.props.notes.map(function(note, index){ return <li className="list-group-item" key={index}>{note.text}</li> })

Tyler McGinnis
Tyler McGinnisinstructor
~ 9 years ago

Thanks Jim!

Whitney
Whitney
~ 9 years ago

Thanks Jim.

So to clarify for everyone getting the "Uncaught Error: Firebase.set failed: First argument contains an invalid key (.key)" error, you can maintain your latest version of reactfire in package.json but you have to make the simple changes as Jim has specified above. That should fix the error.

Dave Fedele
Dave Fedele
~ 9 years ago

this was super helpful!

Farooq Ali
Farooq Ali
~ 9 years ago

Following works for me in Profile.js:

  handleAddNote: function(newNote, username){
    this.ref.child(username).push(newNote);
  },

And in NoteList.js:

    var notes = this.props.notes.map(function(note, index){
      return <li className="list-group-item" key={index}> {note['.value']} </li>
    });
Minfa
Minfa
~ 9 years ago

I saw that in the video the 'addNote' function was created under the 'Profile' component, and got passed as props to the 'Notes' and 'AddNote' components.

I wonder is there any benefits of doing so? In my point of view, it seems more appropriate to move the 'addNote' function into the 'AddNote' component because it what this component should handle, right?

Thanks.

amitgaur
amitgaur
~ 9 years ago

That would require you to move all the state to the AddNote.js : firebase et all..wouldn't really work since you need to update the NoteList whenever you add a note. Thats why a Mixin is used here.

John Bonaccorsi
John Bonaccorsi
~ 9 years ago

I was wondering the same thing, Minfa. Would love to get a response here. What's the benefit? Seems like it only adds more complexity in having to require it at each level as well.

Joel Hooks
Joel Hooks
~ 9 years ago

There is always more than one way to skin a cat, but the profile seems like a "connected" or "smart" component, versus the notes which are "pure" or "dumb" components and just take whatever they are fed.

egghead eggo
egghead eggo
~ 8 years ago

The lesson video has been updated!

Alan Eng
Alan Eng
~ 8 years ago

Great video. Out of curiosity, would it be tough to submit the note by pressing the enter key instead? Any tips or a guide you can suggest for doing this?

Tyler McGinnis
Tyler McGinnisinstructor
~ 8 years ago

Not hard at all and it would be a good learning activity. Here's some help if you get stuck. https://gist.github.com/tylermcginnis/3804a4002e45cd6f0ddd

Adam Romines
Adam Romines
~ 8 years ago

Since refs seem to be frowned upon, is there a good reason to use them here? Is there an alternative that would just rely on state? Still slightly shaky on React patterns. Thanks for the tut and thank you for updating!.

Tyler McGinnis
Tyler McGinnisinstructor
~ 8 years ago

Hi Adam,

That's the first time I've heard about "refs being bad" and I try to be pretty involved in the React community. But to answer your question specifically. You'd need to set a property on the state that's the value of the input field (initially an empty string). Then whenever the value of the input field changes, you'll need to call setState to update the input field. Currently asking around to see if refs are indeed bad, I'll update you if I get any good info.

Tyler McGinnis
Tyler McGinnisinstructor
~ 8 years ago

Just to make sure I recruited someone smarter than me. Refs are fine in the case in the video. "I'd say don't use refs for component instances. That's what I mean when I say "avoid refs". People tend to use refs to instances and call methods on them. That's usually bad. For DOM refs are useful and can be used just fine IMO" - Dan Abramov

Adam Romines
Adam Romines
~ 8 years ago

That makes sense. Thanks! Have a SO upvote for your troubles!

Abha
Abha
~ 8 years ago

Hi, I am not able to access https://github-note-taker.firebaseio.com/ from my code. If I try to access it directly, it returns me an Authentication Required. Do I need special permissions to access this app or do I need to create my own? ( I am currently logged in using my Google Account in Firebase).. This is my first time with firebase, so I am pretty sure I am missing something, but seems like this is just an example of using a service which returns a piece of data. Could you help me?

Tyler McGinnis
Tyler McGinnisinstructor
~ 8 years ago

Hi Abha,

You can access all the data from https://github-note-taker.firebaseio.com/ but you won't be able to access the dashboard since it's under my account. If you want to see the actual dashboard you'll need to create your own project. If you just want to use the data that's in the video without being able to see the dashboard, you can use that same github-note-taker link above.

Tyler

Jeremy Zilar
Jeremy Zilar
~ 8 years ago

Question —

  1. In this line, there are three uses of Add Note. <AddNote username={this.props.username} addNote={this.props.addNote} />

The first is the component, the second is the name of the property that we are passing on to the component, the third is the value of an 'addNote' property that comes from the handleAddNote function in Profile.js and passes (something) over to the Notes component where it ends up here. Is this correct?

  1. I am trying to wrap my head around 'this' at each stage it is used and how the code is using 'this' to pass along data, or how 'this' gets altered when an action is taken. It would help me to visually see how the data is represented at each stage, because it gets a little complex in that Profile.js file.

Is console.log() going to be the best way to view 'this' at each stage?

And thank you — these lessons are really great. Thank you for all the hard work.

Tyler McGinnis
Tyler McGinnisinstructor
~ 8 years ago
  1. That's correct.
  2. Have you checked out my egghead videos on 'this'. They're actually my favorite videos I've done. There are three of them and this is the first one (https://egghead.io/lessons/javascript-the-this-keyword-introduction-and-implicit-binding). console.log is probably your best bet.

Tyler

Jeremy Zilar
Jeremy Zilar
~ 8 years ago

Wow, great lessons! They were very helpful, thank you!

Ruben
Ruben
~ 8 years ago

Great video, but I'm not completely clear on the ref={this.setRef} part. Will the setRef function be called when the input field is being rendered? Where does the ref parameter of the setRef function come from? Is this automatically set to a reference to the input element?

Cheers, Ruben.

Tyler McGinnis
Tyler McGinnisinstructor
~ 8 years ago

Hi Ruben,

Sorry for not making this more clear. Your intuition is correct. The way refs work is that they take a callback function which gets passed a specific reference to the input field. So in this case when setRef is invoked, it's passed a reference to our input field which we set to "note" on our instance. Then when we need the value of the input field, we just get it off of "this.note".

Ruben
Ruben
~ 8 years ago

Thanks for clearing that up Tyler. That makes sense.

Thomas Clark
Thomas Clark
~ 8 years ago

Using firebase 3.4.0, I had to add FireBase Config via public/index.html. I'm no longer getting "firebase is not a constructor" error, but my componentDidMount function is now yelling at me with this error: "Uncaught TypeError: Cannot read property 'child' of undefined" after I removed the firebase URL from the DidMount function. Suggestions?

Gerard
Gerard
~ 7 years ago

Running into this error when posting to Firebase:

bundle.js:26415 Uncaught TypeError: Cannot assign to read only property 'addNote' of object '#<Object>' at Object.handleSubmit

Markdown supported.
Become a member to join the discussionEnroll Today