Learning React.js components, elements, props, state, and events

Learn React.js in this interactive tutorial

Loading The Interactive JavaScript Learning Tool...

Outline:

  • 1 Introduction

    Hello, Welcome to the Learning React lab. Let's write a simple React component. We'll use the class syntax, class Hello extends the React.Component class. Inside the class we define a render() function. The render function returns the HTML that we want the component to display in the browser. Then we use the ReactDOM variable to render this component into the mountNode variable. The variables React, ReactDOM and mountNode are all globally defined for you. Note how we treat the component here as if it was just another HTML element. When we execute this code, "Hello React" shows up on the screen. This is a live editor here on the left which means you can start playing with this code even while I am talking. Try it. To render any changes you do in the live editor click the execute button or press CTRL+Enter. Your first simple challenge is to render the word Easy on the screen using the simple component we defined. Note the challenge box that just appeared with the details of the challenge. When you solve the challenge correctly you can click the continue button.

  • 1.1 Navigation and controls

    You can pause/play a session at any time using the controls you see here at the bottom, you can also control the speed of the audio and show/hide the subtitles with these links. A lab has many sessions and sub-sessions which you can see as links here. You can jump back and forth between sessions as needed. The next session will not start automatically as this is mostly an interactive environment. At the end of each session, you'll get a challenge to validate what you learned in the session, but you can always use the navigator links to jump to any session at any time. Try to do all the challenges though because the better way to learn is to build something with what you're learning. Your challenge for this session is to rewrite the simple Hello component from scratch. So I am going to reset the editor now and I want you to write the Hello component from memory. Try not to cheat and seek backward to see the code, it's really easy, a class that has a render() function that returns HTML. Ready? Go.

  • 1.2 The live editor

    With every new session, I'll be resetting the content of the editor, to start a new example. Sometimes, I'll switch the editor into read-only mode to talk a little bit about that example. The editor will show a darker background like now when it's in ready-only mode. As you can see, I added some styles to the rendered Hello component using the style property, which evaluates a function I defined on the component instance. This style() function returns a plain-old JavaScript object with CSS properties and their values. Now that I'm done explaining what I did, I'll switch the editor back to active mode so that you can experiment with the code Try to make the rendered content blue and see how you can execute the code while the audio player is running. Try to make the font size bigger. When you're ready, click continue to start the next session but remember, when you start a new session the editor will reset to the initial code for that session.

  • -- Signup for Notifications!

    If you wan't to be notified when we publish future interactive labs like this one, you can signup with your email address here. To continue with the lab, click the Continue button in the navigator. The actual code for this signup form is what you see in the editor, it's a simple React component that returns an HTML form, and notice that you can use bootstrap classes here. On form submit, this component issues an Ajax request and actually subscribes the user-typed email address on the backend, and you can see the code to handle the response and errors if any.

  • 2 React components

    The example Hello component we've been using so far is simple. To define a component, we make a class that extends the React.Component class, and define a render function in that class. The render function returns the virtual DOM node that we want React to take to the browser. The component name can be anything. In fact, go ahead and rename this Hello component into Button (instead of Hello), and change the content of the component to return an HTML button element element instead of the div. Use the label "submit" for the button. See the desired output in the challenge box

    Solution

    We make the component return a button instead of the div and use the label "submit" for the button, and we'll rename Hello into Button. Note how we always use a title case for the component's name because otherwise we might get a conflict with actual HTML elements like our button element, for example

  • 2.1 The state object

    We have a Button component, let's add some interactivity to it. Let's make it increment a counter value on every click and display the value of that counter as the button's label itself. Sounds good? So the label of the button is going to be a number that starts as 1, and when we click the button the label will change to 2, 3, and so on. Since we want React to re-render this Button component every time we click the button element, we should place the label of the button on the state of the component. To define an initial state for this Button component we define the instance property "state", which is a simple JavaScript object, and in that object, we'll define a "label" property, and let's initialize it as "Go". Then in the render() function we can use this label value using "this.state.label" which we can put inside a JSX expression like this. Now when we render(), the button will display the label value we initialized on the state. Try to change the label value and re-render and see how the value you place on the state will always be what React renders as the label. The value does not have to be a string, you can place a number in there as well. Try to render the number 1 instead of the string GO using the label's initial value.

    Solution

    So we simply change the label value to be an integer like this. We can use any primitive value here. We can use arrays and objects if we want to. The state object is just a plain-old JavaScript object

  • 2.2 Updating the state on click

    Now that we defined an initial state and saw how to read it, inside the render method, let's change it on click. We'll need to define a click event on the button element. We do so by supplying an onclick property to it. The value of this onclick property has to be a function reference clicks the button. Let's name this function handleClick. We'll define as an instance property on this class so we can get a reference for it using this.handleClick handleclick's job is simple. read the current label's value, increment it, and update the component state with the incremented value. We read the current label using this.state.label, add 1 to that, and this incremented value is what we want to write back to the the state. To update a component state, we use the setState instance method. It takes an object and uses that object to update the state the button's label property will now be the previous label property incremented. Go ahead and test this code, the button should now increment its value on click. Your challenge now is to make the button double its value on click instead of incrementing it.

    Solution

    To double the value, instead of adding 1 to the current value, we just multiply it by 2. Now the label value will double on each click.

  • 3 Components Reusability

    So far we've seen only one component, so let's add another one. Instead of rendering the incremented value as the button label, let's try to render it in another component. Let's call this component Result. I am gonna use a different syntax here to define the Result component. It's called the function syntax because it's basically a simple JavaScript function. This much simpler syntax can be used if we don't need to use a state object inside the component. This Result component will be a stateless one so the function syntax will be good here. Now let's define another component, Root. This time we'll use the class syntax because this component will eventually have a state object. The Root component is a simple one, it'll render both the button component and the Result component. Finally, to get everything to render, we want to change the top level rendered component here from button to Root and now both the Button component and the Result components are showing up. We have not connected them yet we'll do that in the next session, but take a good look at the 2 new components that we just defined, because I am going to delete their code and I want you to rewrite it from memory. It's very simple. A function that returns some DOM and a class that has a render function that returns some DOM And you can use components inside other components. Ready? Go.

  • 3.1 Sharing data between components

    We don't need the button to show the incremented value any more so let's make its label a simple +1 to label its operation. The idea here, when we click the button, we want the Result div to change it's value to reflect the newly incremented value. But we do have a problem here. The label's value that we previously incremented is owned by the button component and we now need this Result component to have access to that value. The Result component is currently a sibling of the Button component in our DOM tree. To make the incremented value available for both sibling components, we need to move it one level up to the parent Root component We move both the state object and the event that's handling the update call on the state. However, with this move, the names we used before don't apply here. It's not the button's label any more. Let me rename that into counter instead. To get this counter to appear in the Result component we pass it to the component as a property, I'll do counter equals this.state.counter. Now the Result component receives a property called counter and we can use the first argument to the component, which I'll name props, to read that value. We read the counter value using props.counter. The initial value 0 shows up in the Result component div now. I am going to also rename handleClick here to incrementCounter to make the next change easier to understand. The handleClick event on the button component is not going to work any more because we moved it. But we can have the Root component tell the Button component what function to execute on Click by also passing a prop to it but this time, the prop value we pass is a function reference instead of a primitive value. onClick is this.incerementCounter. Inside the button component, we'll have the onClick event fire this received prop which we can access here using this.props.onClick. Note the small difference in accessing the props object between the class syntax and the function syntax for components. The Button component is no longer a stateful component so we can actually convert it into a function component here but in the next session, we'll do one other feature that's better suited for the class syntax so I'm gonna keep it on that syntax for now. Quick review. We moved the state's counter value to the Root component, passed it down to the Result component using a prop, and passed an onClick handler function reference to the button component. Now the button component can basically tell the Root component to increment the counter value on each click Your challenge now is to render 2 buttons instead of one. Don't define any other components. Just re-use what we have. Both buttons should increment the displayed counter value in the Result component

    Solution

    We simply re-use the button component inside the Root component like this We can re-use a component as many times as we want. In the next session. We'll see how to customize a component using its props object

  • 3.2 Customizing a component with props

    Let's make those buttons able to increment any value not just one, to make it more reusable and use it to create, for example, a +5 button, a +10 button, and so on We'll first need to update this static +1 label to something dynamic Something we can pass to the component form its parent component. So how about we pass the amount this button should increment as a property here, I'll call it increment and pass it a numeric value, we'll pass the other button an increment of 5 instead and let's add one more button with an increment of 10 The button's label can simply use this value as this.props.increment. Now the UI is rendering 3 buttons with different labels, but they're all still incrementing the counter value with 1 So to account for their now customized increment value, we need to customize the event handler and make it somehow send this increment value to the incrementCounter call so that we can use it in place of this hard-coded 1 here I want you to take your time and try to do that on your own first This challenge is really just JavaScript, so don't be intimidated by it. You can always check the solution to see how I solved it.

    Solution

    This problem can be solved in many ways. We first need to make the incrementCounter method receive the incrementValue as an argument and we'll use this incrementValue instead of the one here Now to make each button send it's correct increment value we can simply define a local function on the component instance, call that function handleClick, and in that function call the parent onClick function using the increment property of the button component instance and now we wire the onClick event here with this.handleClick This way the button component will send its own increment prop value to the incrementCounter method on the Root component. Go ahead and test our code so for the buttons should increment by 1, 5, and 10

  • 3.3 Review (and a final challenge)

    Let's now do something a little bit more challenging but let's review first. We defined 3 components, both the Button component and the Root component used the class syntax. We use the class syntax when we need to use the state object or when we need to define custom event handlers and a few more cases we'll cover in future sessions You should use the function syntax that we used for the Result component whenever the component is purely presentational. We made the Button component re-usable by passing down an increment property. This allowed us to create many buttons with different increments Since we moved the counter value to be part of the Root component state we needed to pass the Button component a function handler This function handler gives the Button component access to the incrementCounter method on the Root component The Button's component onClick event function can now send the parent incrementCounter method the increment value that it should use Your challenge now. Try to customize the button component a bit more. Make it receive an operator as a prop, so we can for example make it multiply the result with its value instead of incrementing it. Let's support the 3 operations, adding, subtracting, and multiplying

    Solution

    So we can pass the button an operator property, and let's do plus for the 1, multiply for the 5, and subtract for the 10. This value we pass to the button is no longer an increment, so we should rename this now. I'll call it operand. Similarly, since we're not just gonna be incrementing the counter now let's rename this method into updateCounter instead. Names are important, always update the names of your variables to reflect the changes you're making to the logic. In the button component, we'll use the new operator and operand props to display the label and we'll make the onClick handler report both of them up the updateCounter method. The update counter now receives both operator and operand. and it should decide what to do with the operand based on the operator. Let's define a property on the Root instance, call it supportedOperations. This property will be an object indexed by the supported operator. every value will be the function that we should execute for that operator. The function receives the current counter and the operand value and returns the result of doing the operation on the counter. Now in update counter, we define the newCounter value. We first pick the supported function from the supportedOpreations object. Then we invoke that function with the current value and the passed in operand value. We'll then update the state with the newCounter value. You can test that now.

  • -- Thanks!

    If you have any feedback about this lab, whether it's about the tool you used here, or the content, or anything else, good or bad, please let's me know. Email me or tweet to me directly, you can also join our slack chat at slack.jscomplete.com If you liked this. Please share it to help us get to a bigger audience. Thanks for taking the lab. See you in the next one.