This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

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

10:26 React lesson by

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.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

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.

Avatar
David

A quick question, is the username prop really needed in the AddNote component ? It doesn't seem to be used:

[https://github.com/tylermcginnis/github-notetaker-egghead/blob/07-managing-state-in-children/app/components/Notes/AddNote.js]

(Also the given link to the code has a spurious space in the url)

In reply to egghead.io
Avatar
Joel

Spurious space obliterated! Thanks :>

In reply to David
Avatar
Tyler

Hi David,

Good catch. It's indeed not needed. Not sure why I threw that in there.

Tyler

In reply to David
Avatar
Kendall

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']}

Avatar
Marcelo

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 "]"

Avatar
Richard

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

In reply to Marcelo
Avatar
Joel

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

In reply to Junhak
Avatar
Jim

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>
})

In reply to Marcelo
Avatar
Whitney

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.

In reply to Jim
Avatar
david

this was super helpful!

In reply to Jim
Avatar
Farooq Ali

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>
    });
In reply to Jim
Avatar
Minfa

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.

Avatar
amitgaur

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.

In reply to Minfa
Avatar
John

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.

In reply to Minfa
Avatar
Joel

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.

In reply to John
Avatar
egghead.io

The lesson video has been updated!

Avatar
Alan

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?

Avatar
Tyler

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

In reply to Alan
Avatar
Adam Romines

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!.

Avatar
Tyler

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.

In reply to Adam Romines
Avatar
Tyler

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

In reply to Tyler
Avatar
Adam Romines

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

In reply to Tyler
Avatar
Abha

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?

Avatar
Tyler

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

In reply to Abha
Avatar
Jeremy Zilar

Question —

  1. In this line, there are three uses of Add Note.

    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?

  2. 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.

Avatar
Tyler

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

In reply to Jeremy Zilar
Avatar
Jeremy Zilar

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

In reply to Tyler
Avatar
Ruben

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.

Avatar
Tyler

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".

In reply to Ruben
Avatar
Ruben

Thanks for clearing that up Tyler. That makes sense.

In reply to Tyler
Avatar
Thomas Clark

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?

Avatar
Gerard

Running into this error when posting to Firebase:

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

We've talked a lot in this series how the state of these three components is being managed in this outer profiles component.

Main 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 handleAddNote. What it's going to do is it's going take in a newNote, and then we are going to update Firebase with the newNote.

components/Profile.js

handleAddNote: function(newNote){
    // update firebase, with the newNote 
},

What's going to happen is, because we have bound our note's state to our childRef here, whenever we update this endpoint, those changes are automatically going to be pushed through to our state.

components/Profile.js

var childRef = this.ref.child(this.props.params.username);
this.bindAsArray(childRef, 'notes');

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).

components/Profile.js

handleAddNote: function(newNote){
    // update firebase, with the newNote 
    this.ref.child(this.pops.params.username).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 newNote,

components/Profile.js

this.ref.child(this.pops.params.username).child(this.state.notes.length).set(newNote),

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 newNote 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.

components/Profile.js

componentDidMount: function() {
    this.ref = new Firebase('https://github-note-taker.firebaseio.com/');
    var childRef = this.ref.child(this.props.params.username);
    this.bindAsArray(childRef, 'notes');
}

Now what we need is we need some way to get this handleAddNote 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.

components/Profile.js

<Notes
    username={this.props.params.username}
    notes={this.state.notes}
    addNote={this.handleAddNote} />

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

Notes/Notes.js

proptypes: {
    username: React.PropTypes.string.isRequired,
    notes: React.PropTypes.array.isRequired,
    addNote: React.PropTypes.func.isRequired,
}

As we talked about with the NotesList, 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.

Notes/AddNote.js

var React = require('react');

var AddNote = React.createClass({

});

module.exports = AddNote;

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.

Notes/AddNote.js

propTypes: {
        username: React.PropTypes.string.isRequired,
        addNote: React.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="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="form-control", which is a Bootstrap thing. Let's also give it a placeholder="Add New Note".

Notes/AddNote.js

render: function(){
        return (
            <div className="input-group">
                <input type="text" className="form-control" placeHolder="Add New Note" />
            </div> 
        )
    }
});

Now what we want to do is, if you'll remember, we have our handleAddNote function. We eventually want to give that a newNote. That newNote 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.

Notes/AddNote.js

<input type="text" className="form-control" placeHolder="Add New Note"/>

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 this.setRef().

Notes/AddNote.js

<input type="text" className="form-control" placeHolder="Add New Note" ref={this.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.

Notes/AddNote.js

setRef: function(ref){
    this.note = ref; 
}

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.

Notes/AddNote.js

<span className='input-group-btn'> 
    <button className="btn btn-default" type="button" onClick={this.handleSubmit}>Submit </button>
</span>

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.
Notes/AddNote.js

handleSubmit: function(){

},

Let's give it Submit, and then we'll close our button.

Notes/AddNote.js

<div className="input-group">
        <input type="text" className="form-control" placeHolder="Add New Note" />
        <span className='input-group-btn'> 
            <button className="btn btn-default" type="button" onClick={this.handleSubmit}>Submit </button>
        </span>
    </div>

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.note.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.note.value equals an empty string, just to clear that input field. Then I'm going to call this.props.addNote, passing it our newNote.

Notes/AddNote.js

handleSubmit: function(){
    var newNote = this.note.value;
    this.note.value = '';
    this.props.addNote(newNote)
},

Remember, addNote, we're getting from our Profile component, so when we call this.props.addNote, we're going to that newNote, pass it to handleAddNote, which should then update Firebase.

components/Profile.js

handleAddNote: function(newNote){
    this.ref.child(this.props.params.username).child(this.state.notes.length).set(newNote)
}

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.

Notes/Notes.js

var AddNote = require('./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 handleAddNote function, which is right here, which is going to eventually, when it gets invoked, take in a newNote, 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.

Notes/Notes.js

render: function(){
    return (
        <div>
            <h3> Notes for {this.props.username} </h3>
            <AddNote username={this.props.username} addNote={this.props.addNote} />
            <NotesList notes={this.props.notes} />
        </div>
    )
}

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.

Notes/AddNote.js

handleSubmit: function(){
    var newNote = this.note.value;
    this.note.value = '';
    this.props.addNote(newNote)
}

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

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?