What do you want to learn?
Skip to main content
React.js: Getting Started
by Samer Buna
Learn the basics of React.js, and build an in-browser, math skills kids' game from scratch with it.
Resume CourseBookmarkAdd to Channel
Table of contents
Your First Component
So far we've only seen one component, so let's add more. Let's split our one component into two, the button to be just the incrementer, and let's add a new Result component, which is going to just display the value of the counter. Since this Result component is a presentational one with no state of its own, we can safely use the function component syntax here. Define a function named Result, and just put a placeholder div in there. Notice that this new Result component did not show up because I have not included it in the rendered element yet, I just defined it. Let's include it. Let's create a new App component to include all the other components. I'll use the class component syntax for this one because in a minute we're going to need to introduce a state object in this new App component. We'll make the App component render a simple div, and inside that div we'll render both the Button component and the Result component. The parent div here is not actually optional. A React component can only return one element, you can't return multiple adjacent elements, so when you need to return sibling elements you have to wrap them in one parent element. Now we can change our ReactDOM.render call to render the new App component instead of the Button component. Execute and we now have both the button and the Result component showing up in the DOM. Since we no longer need the button to display the counter value, let's just change its label to +1. When we click that button now, we want the Result text here to change. But we have a problem. The counter is currently a state element on the Button component, and we need to access it in the Result component, which is a sibling of the Button component, so this is not going to work. The state of a component can only be accessed by that component itself and no one else. So to make this counter state accessible to both components, we need to move it one level up and put it on the parent of both sibling components, which is in our case the App component. We just move this state class property line down to the App component. The logic for this handleClick function will need to change. We'll come back to that in a minute, let's just comment it out for now. I used Cmd+/ here to toggle commenting out these lines together. In the App component, since the counter state is now here, we now need a function on this level to handle updating the state. We'll call it incrementCounter. The logic for this counter is actually the exact same logic we had before in the Button's handleClick function. It'll update the state to increment the counter value using the previous counter value. The onClick handler on the button element now has to change. We want it to execute the new incrementCounter function on the App component, but a component can only access its own functions, props, and state. So, to make the Button component able to invoke this incrementCounter function, we can pass a reference for incrementCounter to the Button component. We can name this reference anything. I'll name it onClickFunction and pass it this.incrementCounter, which again is just a reference to that function. Now inside the Button component we can use this reference directly in the onClick value. It will be a prop on this component, so we access it with this.props.onClickFunction. This onClickFunction property allows the button to actually invoke the App component's incrementCounter function. This is powerful. Basically, when we click the button, the Button component reaches out to the App component and says, Hey parent, go ahead and invoke that incrementCounter function for me. So the syntax here to access properties of a class component is this.props, remember that. Our Result component needs to read the counter value, and we can use the same trick to pass in that counter value from the App component as a property to the Result component. Again, we can use any name here. I'll just simply use the same name, so counter is this.state.counter in the App component. Within the Result component we can just read the property as props.counter where props is the only argument this Result function component receives. Note how the syntax for accessing properties on a class component is different than that for accessing them in a function component, but the idea is the same. We can test now. The Button component invokes a function to update the App state, which in turn is read by the Result component. Note that from the point of view of the Result component, the counter is not a state, it's just a value that the App component is passing to it. The Result component will always print that. The state changes on the App component level. The place where you define the state is an important question to answer when designing your React components. Components are all about reusability, right? So let's make this Button reusable by changing it so that it can increment with any value, not just one. So we'll use a new Button component to create a +1 button, a +5 button, and so on. We need to upgrade this +1 here to something dynamic. It will be a property of the component, the amount to increment. Let's name this amount incrementValue and pass it a numeric value. In the Button component we can use that property as usual with this.props.incrementValue. So now we can reuse this component with different increment values. Let's do a +5, 10, and 100. The UI rendered four buttons now with different labels, but they're not going to work yet. In fact, all of them would still increment with a +1 at this point because we did not change their handlers. This incrementCounter function will now need to receive an incrementValue, and instead of using +1 here it will use that value. Every button component needs to invoke the onClickFunction with an argument now. We can simply use an inline function here and invoke the onClickFunction inside of that with the component's incrementValue. So we wrapped the action that we want to happen with a function for the handler, and through the magic of closures, this will work just fine. Now the buttons will increment the counter with their respective values. Another alternative here is to use the bind function and create new functions that remember their arguments. While these solutions work fine, they are not the recommended way to handle this situation. The reason being, when we use the bind method or the function wrapper method, we're actually creating a new function for every rendered button, and that can be avoided. Instead, we'll resurrect this handleClick function. The onClick handler will just call that, this way it's not creating a new function every time. Inside handleClick we'll replace what we had before with the same call to this.props.onClickFunction with the argument of this.props.incrementValue. Although this is a bit more code, it's actually easier to understand and it's more efficient. In fact, in this simple example, the need to invoke a function handler that uses a prop on the component is enough reason to use the class component syntax over the function component syntax. This is why I kept the Button component on the class syntax, although it does not have a state object on its own.
Working with Data
We've seen simple components, we've worked with multiple components, and we've seen how and when to use state and properties of components. However, we haven't really worked with any real data yet, so we're going to be doing exactly that in this module, and we'll use the GitHub public REST API for it. We're going to build a simple GitHub card component that displays information about a GitHub user. Once we have a card component, we're going to explore how to render multiple cards for different users and have that driven from an array of objects. Then we'll add a simple input form to add a new GitHub card to the UI for a username that we read from the input using the GitHub API. Before we begin, if you don't have the react-devtools extension installed already, find it now and add it to your browser. It adds a tab in your Dev Tools console, which gives us a screen that is super helpful. It allows us to inspect and interact with the React application. You can actually go to any site that uses React and see how they structure their components here with this tool. For example, the netflix.com app can be inspected here, you can see their components and you can see every component's props and state, you can even change the state of any component directly here. For example, let's see what would happen if we change this showDisclosure state element to true. React re-rendered this component and showed a component that was not previously part of the app. Familiarize yourself with this react-devtools extension. It's going to be your best friend when you start writing React applications.
Build a Github Card Component
Taking Input from the User
We've built a few simple React components in this module, a Card component to render information about a GitHub user, a CardList component to convert an array of records into an array of the Card components, a Form component to read input from the user, and an App component to manage the relation between all the other components. We've managed the records array as a state element on the top-level App component, which allowed us to share data between multiple components, and it allowed us to append new cards to the UI by simply appending the GitHub API response to that state element. In the Form component, we explored how to access an element in the DOM from React directly using the special ref attribute, and we explored how to read from an input element using React's state itself with the help of the onChange event. Components written with this latter method are known as controlled components. It's important that you understand the concept of passing properties from the parent to the child, and that those properties can be regular primitive values or they can be function references that the child can invoke up the chain on the parent component itself. This is what we did to pass the information in the Form component to its parent, the App component. In the next few modules, we'll build a simple game for kids. Here's a preview of it. The player gets a random number of stars here between 1 and 9 and a set of numbers from 1 to 9. They select numbers that sum up to the number of stars, and keep doing so until all the numbers are used.
Building the Game Interface
Over the next few modules, we're going to build an in-browser math game for kids. I am calling it Play Nine. You can see the final product here. When the game starts, you get a random number of stars between 1 and 9, and you have the set of numbers in the bottom frame that you can use. You can select one or more numbers that would sum up to the value of the random stars. The objective is to correctly use all the numbers here in the bottom frame. If you end up with a number of stars that has no possible correct combination, you get to redraw, which you can do five times. After that, if you still end up with a number of stars that has no possible correct combination out of all the remaining numbers, then you lose the game. Let's play a few rounds. We have 3 stars here, so we can do either 3 or 2 and 1. Check the answer. We get an indication that the answer is correct, and when we can click again to accept the answer, both 1 and 2 will be marked as used, and we'll get a new number of stars. So let me show you how a wrong answer will look like; you get a wrong button here. So undo this. Let's pick 9, pick 7, 3 and 6 is 9, 5 and 4, and now we have 3 stars, but we can't select any correct answer here, so this is the case where we can redraw and see if we can get a correct answer. And with luck, we get 8 stars, so we can select the last number and win this game, and we can play again. So if I didn't have any redraws, if I had 0 redraws and I tried to win this game, that would be hard. Let's try it real quick. Seven, 8, we can do that, 1 we can do that, 9 we can do that, and 1, we can't do that anymore because we've selected 1 already, so I got Game Over! We're going to build the interface of our game in this module. It will be mostly be markup and CSS, but we're going to do those in React, and we'll also render dynamic sections with data collections instead of hardcoding values. We'll build the whole game in jsComplete's REPL here because this course is not about configuring React. Using the REPL will allow us to focus on just the React API and keep the course short. We're going to need to style the game a bit though. Since this is not a course about styling, I'm going to just use the Bootstrap framework for most of the styling. This editor has a CSS mode, which we can use to apply any CSS to our markup. We'll use that briefly to simplify our game logic. I'll also use Font Awesome for all the icons we'll need in the game. You don't need to include either Bootstrap or Font Awesome, they're both used by this editor and are available to the playground as well.
The Main Game Component
Let's get started right away by thinking about the top-level components that we need for this game. We render a component with ReactDOM.render, something in the playground's mountNode element. I like to always start with an App component and then create whatever components I come up with inside of that App component. This is sometimes not needed, and I do sometimes get rid of this App component eventually, but having that helps me test my actual application if I need to with more flexibility. For example, let's name the game's main component Game. This is the component that will manage all the state of a single game. Let's add a header for the game, Play Nine, use this Game component inside the App component, which is the one that will render in mountNode, test that this structure is working so far without problems. Now during the development of this game, to test that the state of the game is isolated, I might render two copies of this Game component in my App component. This is especially useful when you start working with a third-party state container like Redux for example. There is one other benefit to having a top-level container component like the App component here, and that's when we need to completely reset the game. The easiest way to do so is to un-mount and re-mount it. The App component can help us there.
The Stars, Button, and Answer Frames
Let's create the subcomponents for this game. The first one would be the Stars component. This is where we're going to render the random number of stars, but for now, let's just build the markup and make sure things look good. So this is going to return a div, let's put placeholder dots in there, and go include this component under the Game component. Make sure the dots got rendered. Now copy this Stars component, and let's create two other components, one for the button that's going to show up in the middle, and another for the Answer box that will be on the right side of the button. Include all components under Game, and make sure they all render. Let's render some actual data now. We can use the awesome Font Awesome to render a star icon inside the Star component, we just use an i element with classes of fa and fa-star. Let's render a few stars here. In the Button component, we'll render a simple HTML button element with a label of =. The Answer component starts empty, and then it will get numbers as the user plays. Let's keep it as is for now and change the layout of what we have so far. I am going to do some Bootstrap and CSS next, and this course is not about that, so you can skip the rest of this clip if you want and use the next GitHub gist at the beginning of the next clip. Alright, instead of wasting a lot of time on CSS, we'll simply implement the main layout using Bootstrap 4, which is already included in this playground. All we need to do to make these 3 components show up in a single row, is to put them in a div that has a class of row, then make every component's top level div a class of col, and we can define sizes here on a grid of 12 cells. So let's give the Stars component 5 cells, the Button component 2 cells, and the Answer component 5 cells, and give the Game component itself a class of container. Let's add an hr element here below the title. The last section we want to add markup to is the bottom frame that holds the list of numbers to play with. Let's create a new component for that, Numbers. This will return a div, I'll give a Bootstrap class of card to make it into a box, and let's also align the content of this div to center. This card div will have one div that holds the numbers in one line; we'll use a span element for every number. This is going to hold all the numbers between 1 and 9, eventually. Render this Numbers component under everything, test, numbers are showing up. Let me add a line break here to give it some space. This is all the markup we need so far and it does look ugly, but we're not going to spend a lot of time styling this game, but we are going to use some CSS to help us simplify the logic of the game. So I'm going to use a global style sheet for all the CSS that we need, just to keep things simple, because this is going to be a big example, and I want you to focus on the main React API itself. So we can edit the global style sheet for this application by clicking this icon. I am not a CSS expert, but let me try to quickly make this markup look a little bit better. The stars are too close to each other. We can simply style the .fa-star class to style them here in the global style sheet. Let's give them a little bit of a margin and increase their font size. The numbers are also very close to each other. To style them, I'm just going to style the span element directly here. Make the display inline-block, also some margin, align the text center, give it a background, and I'd like to make the numbers into circles instead of squares, which we can do simply by giving it a width and a 50% border radius. Looks good. One last thing here, since these spans are going to be clickable, we should make the UI indicate so. We can simply add a cursor pointer property to show the familiar pointer cursor when we hover over them. Every number will have three different states; it can be not selected, or selected for the current answer, or already used and can't be selected again. Let's define the visual differences between these three states with CSS. I'm going to give the second number a class of selected, and the third number a class of used, and style those differently. For the selected class, let's give it a lighter shade of gray, for both background and color. For the used numbers, since this is a successful pick, let's give them a shade of green. Also when you select or use a number, you cannot click it again, so I'm going to change the cursor here to indicate that. This not-allowed value makes the cursor show up like this. This is good enough for now; let me stop pretending that I know CSS and let's write some code.
Random Number of Stars
Let's make the Stars component render the actual number of stars as it should do in this game. For starters, let's make it render the stars from a variable. I'm going to call this variable numberOfStars and start it with a value of 5. So to render a dynamic number of stars from a variable like this, we need to iterate over that variable's value, and for every iteration, add a star. There are a few ways to do that; the simplest is to just use a for loop, from 0 to our variables value, and inside that loop, push one star to the stars array. In the component's render method, we can just use the stars array directly in curly brackets like this. When React sees this array, it will just join it and render it correctly. Even if the array is an array of values, like numbers, React will just render every number in a text node, which is pretty cool. Now that we're rendering the stars from a variable number, we can go ahead and switch the test value with an actual random number. We can use Math.random here, which gives us a random number between 0 and 1, and if we multiply that by 9, that will make it between 0 and 9, it would never really produce a 9, the max here is 8.99999, but since we should not have a 0 in the result any way, we can just add one to this expression and take the floor of the random value, which makes it between 1 and 9. We can test this value, and we see that we're getting different random numbers of stars in the UI every time we refresh.
The Numbers Frame
We finished most of the markup of this game in this module with the help of Bootstrap, Font Awesome, and some CSS. We explored how to render a random number of stars in the game's top-left corner. The Button component and the Answer component are both just static markup so far, and in the bottom card, we rendered the 9 numbers and made them show up in clickable circles. Now it's time to add the interactive actions of the game, which is what we will be doing next.
We're going to implement the numbers selection in this module, which is the core of how a user would start playing the game. They're going to see the random numbers of stars, and select one or more numbers that would sum up to that number of stars, and they will do that by clicking on the circles in the bottom Numbers component. When a player selects a number, we want that number to show up in the Answer component, and we want it to be disabled in the Numbers component so that the player can't select it again. When they accept an answer, we will reset this Answer component. We will be managing a state of selected numbers for that, and we're going to write a function to add a number to that state, and also remove a number from that state. This state element is going to be a regular array, and resetting that is as easy as reinitializing it with an empty array.
The State of Selected Numbers
Let's figure out how to select a number to be considered for an answer to the current stars challenge. When we click a number we want it to be selected in the Answer component, and we want to display it as selected in the Numbers component, so we want React to trigger a re-render for both the Answer and the Numbers components. To trigger a re-render, we need to place something on the state and modify it with an action. Since both Answer and Numbers components need to be re-rendered, it makes sense to place the state elements on the parent Game component itself. We'll initialize a state object, and the first element I need on the state right now is a list of selectedNumbers. I'll just put them in an array for simplicity. I usually use objects to hold list, because they're faster for lookup, but arrays are okay as well for very small data structures. I'm going to push a number to this array whenever the player clicks on a number in the Numbers component to select that number. So I have two main different tasks from this point. Task 1 is to figure out how to push a number to this array on click, and task 2 is to figure out what the UI will do based on numbers stored in this array. Those two tasks are actually totally separate in a React application, and we can code them independently from each other. And more importantly, we can test them independently from each other. I always like to make the UI respect the data we have or will have on a state element, task 2, and I just fake the data element manually. Then, once the UI is behaving exactly like I wanted it to based on that fake data, I'll remove the fake data and implement the task to update the array with actual data based on events, task 1. I find this approach much easier to test. So I'll consider the numbers 2 and 4 fakely selected here. This state element is needed by two components; the Answer component needs to display all the selected numbers, and the Numbers component needs to disable a selected number so that the player cannot select it one more time. For a component to access a state of its parent component, the parent needs to pass the state into the child component as a property. Let's use the same name here for the property. Pass to the Answer component selectedNumbers prop that's set to the value of this.state.selectedNumbers. Inside the Answer component, we now have a prop called selectedNumbers, and it will have the value of the game's selectedNumbers array. So we map this array into an array of span elements to represent those numbers in the Answer component here, and don't forget the key prop here because this is a dynamic list of children. We can either use the number itself here as a unique key, but I like to always keep the id and the actual data element in the UI different. That's why I used the index of the element. Ideally, instead of relying on the position of the item here to identify it, we should use objects and have a unique id attribute for every object. But to keep things simple, I'll stick with a simple array here. Test now, 2 and 4 should now show up here. And just like we passed the selectedNumbers state down to the Answer component, we also need to pass it to the Numbers component. We can use the same syntax for that. Inside the Numbers component, we need to check every number while we render it. If that number is in the selectedNumbers array, we need to render it with a different class, the selected class if you remember from the previous module. We just gave this Numbers component access to the selectedNumbers array, so it can implement that logic on its own. We'll add a className attribute here, and make it compute its value with a function to which we simply pass the value of the current number. We can define this function within the Numbers component itself to keep things simple. It will receive a number and check if that number is included in the selectedNumbers array that's now part of the component's props. If so, this function will return the proper className for the case, which is simply selected. The props syntax here is just props, not this.props. If only I had a dollar every time I made this mistake. Now the UI completely respects the state of selectedNumbers. They'll show up in the Answer component, and they'll change to the disabled state that we styled for them here in the Numbers component. Now we can remove the fake data and implement the original task to click a number to select it, which is what we'll do next.
Selecting a Number
We're ready to click on a number to select it. Let's define a new function to handle that. I'll call it selectNumber. This needs to be on the top-level Game component as it will work with the selectedNumbers state. However, we need to invoke it from the Numbers component, so we'll need to pass it there as a prop. I'm going to assume this function is going to receive the clickedNumber as an argument. The selectNumber function implementation is straightforward; it will change the state of selectedNumbers, and add the newly-clicked numbers to that state, and we can use array and concat here to combine the new clicked number with the current array of selected numbers. And that's it. Note how I again used the function argument here for setState because my update operation depends on the previous state. To make the Numbers component able to invoke this function, we pass a reference to it as a prop. Now inside the Numbers component, we have access to this selectNumber function through the props of the component. We'll need to invoke this function every time we click on a number span element here, so we need to define an onClick handler, and what we really want to do here is call props.selectNumber, passing in the current number here, but we can't do that directly. Remember that the onClick handler needs a function reference, not a function call. The easiest way to do that for our case is to wrap what we want to do with an inline function reference like this. Arrow functions made this so much easier. This is good enough for our case here, but remember that we're creating a new function here for every number. When that becomes a problem, the right way is to upgrade this span element here into its own class-based component, and use an instance function instead. But this will do for our simple case. Test now. When we select a number, it gets correctly presented in both the Numbers component and the Answer component. But we do have a problem to solve next. We've actually introduced two problems so far. Can you identify them? First, every time we select a number, the Stars component renders a new random number of stars. Now, imagine that you're being interviewed for a React job, at this point I'd be asking you why is this happening? I'll let you think about that for a while. The second problem that we have so far is that we haven't really blocked the selection of a selected number, we can keep selecting them over and over. Let's fix those two problems right away. Quick advice first. Whenever I identify a problem like this here in the game, pause the video and try to solve it yourself, then compare your solution with mine. Your solution might actually be better than mine, ask me if you're not sure why I did something a certain way. Alright, problem number 1, the changing number of random stars. We get a new random number of stars every time we select a number because React re-renders the Game component, which re-renders all of its children. This means the Stars component also gets re-rendered every time we select a number, and every time we re-render the Stars component, the whole function is invoked, and the numberOfStars variable will get a new value. To solve this, we need to move this numberOfStars variable up one level and maintain it on the Game component itself. So we can use it here in the Stars component as a prop instead of a local variable. Now later on in the Game implementation, when we're ready for a new set of random stars, we do want React to re-render the Stars component. So we'll put the randomNumberOfStars as a value on the state of the Game component itself, initialize it with the same random expression, and pass it to the Stars component as a prop to match how we used it. This way, the number of stars will change in the UI when we update the state of the Game component with a different value of randomNumberOfStars. Test now and make sure that when you select a number, the number of stars does not change. Let's now fix problem number 2. We have many choices here, the simplest is to introduce an if statement before we mark the number as selected. If the number is already selected, then we want to do nothing and return. That's it. I broke the game though because I cannot select numbers any more. Moments like these is when you'll wish that you've written some automated tests for your components. We've introduced only one line though, so it's easy to find the typo here and see if that was the problem, and it was. Now we can select and we cannot re-select a selected number. So far so good. Let's now give the user an option to change their current answer.
Changing The Answer
Enhancing the UI
I'm going to do a little bit of refactoring here before we proceed. We're reading selectedNumbers out of the state here multiple times; I am going to reduce that into a const, along with the randomNumberOfStars property as well. Both of these can be de-structured out of the state object, and then we can use them directly in the code without repeating this.state every time. I want to do one minor enhancement to the UI before we implement the logic to win or lose the game. The check button should start as disabled and only gets enabled when we select a number. This means the Button component will need access to the selectedNumbers state, so we'll give it that. In the Button component, we can define a disabled attribute here. The value of that would be when the length of the passed in selectedNumbers array is 0, in that case the button should be disabled. And React is smart here, it will just not include the disabled property at all if the condition is false, which is what you would expect. Testing now, the button got rendered as disabled, and it gets enabled when we select a number. Let me actually give this button the Bootstrap class of btn to make it render bigger, and now the features for interacting with the numbers in the game are in good shape. What we need next is to implement a way to check and accept a correct answer, and reach a winning or losing state. This is what we'll do in the next module.
We've made good progress on implementing the first few functions for a player to play the game, they can now select an answer, and that would update the visuals in the UI to represent the selection and disable the selected numbers. Players can also change their answers, just by clicking on those selected numbers in the Answers component. We've also made few enhancements to the code and the UI. For example, if we don't have any selected numbers, then the Check Answer button has nothing to do, so we disabled it for that case. In the next module, we're going to finish this game. The features we need to are, to verify an answer, accept an answer, and add states to represent winning or losing the game.
We're going to finish the game in this module, and to do so we need a few important features. We need a way for the users to check an answer, accept an answer, and figure out if there are no more possible solutions left. We're going to also implement a way for the player to redraw the numbers of random stars, and we'll see how many redraws we should give the players. The final component that we want to add to the game is the one that would display the done status, and give the players a way to play again.
Verifying an Answer
So far we've implemented a way for the player to select an answer, and they can also change the answer. Now is the time to check the answer, which is what will happen when the player clicks the equal sign button. For an answer to be correct, the sum of all selected numbers should equal the current numbers of random stars. Both of these values depend on the main state, so let's create a checkAnswer function in the Game component, and inside that function we need to do a calculation to see if the current number of stars equal the sum of all selected numbers. Now usually we do not place any values that can be calculated on the state, but to keep things simple, I'm going to store this calculated value on the state because I need it to trigger a UI change. When this calculated value is true or false, I want to indicate that in the UI. So we need a setState call. This will depend on what we have in the state, so function argument. The answerIsCorrect if the current randomNumberOfStars equal to the sum of the current selectedNumbers, which we can calculate with a simple reduce call with a sum callback, and we should start that with 0 in case the selectedNumbers array was empty. We'll need to initialize this new answerIsCorrect state with a null value, with null here meaning there is no answer being checked for correctness yet. It might be better to actually define an answerState here instead of relying on null for logic, but I'll keep that as a challenge for you to do among many other opportunities to make the code of this game more readable. Next, we need to pass both the checkAnswer function reference and the answerIsCorrect state element to the Button component as it's going to be the one who actually uses them. We'll de-structure the answerIsCorrect value from the state as we do other values here. Inside the Button component, depending on this new answerIsCorrect property, we need to render a different button. Let's create a button variable to hold the UI to render and simply use a switch statement over the answerIsCorrect prop. We have three cases here, when answerIsCorrect is true, false, and the default case, which means the button is in its normal initial state. So for the default case, we want to render the button exactly as we have it now; I'll copy this into the default case, assign it to the button variable. Now for the case when answerIsCorrect is true. This means we clicked the equal sign button and our answer was correct. Let's render a different button for that case. I'll remove the disabled attribute for now, make this a success green button, and render a proper Font Awesome icon here; let's go with a checkmark. For the false case, we'll render another button variance. We'll make it red with a btn-danger class, and render an x icon with fa-times. Finally, we need to replace the original markup in the return section to use our now dynamic button variable. To complete this feature, we need to make the equal sign button invoke the checkAnswer function that we wrote, which we passed already to this component, so we can just define an onClick handler here with a value of props.checkAnswer. We can now test. Select the 1, check answer, correct. One more time, select something wrong and check answer, all good. The next feature would be to accept an answer. We could have actually combined verify answer with accept answer, but keeping them separate allows for some more flexibility while playing this game.
Accepting an Answer
Now we have a way to verify an answer, however, the game goes on after that, so if we have 5 stars, select 3 and 2, check that answer, correct, we can still select other numbers. This also is a problem when we check a wrong answer. What we should do here is if the player selects a number is to reset the answerIsCorrect state, and we can do that inside the selectNumber set call. Set answerIsCorrect to null, and we should also do the same inside the unselect number function. Let's test that. Wrong answer. You can change the answer, and the answerIsCorrect state resets now, and even on a correct answer, you can still choose something else. Let's now create a way for the players to accept an answer. This will be another top-level function on the Game component, but for such a feature to exist, we need to introduce a new state, because the action for accepting an answer should mark the accepted numbers as used, and that's what eventually will determine if the game ends successfully or if the player should get a game over. The UI will need to be re-rendered when we accept an answer to show those accepted numbers as used. Let's initialize a usedNumbers array on the state. Start it with some fake data for testing the UI first. In the render method, let's read usedNumbers into a constant, and pass that constant into the Numbers component. Now inside the Numbers component, we need to change the logic of the numberClassName function. If the number is used, we want this function to return the string used instead of selected. We'll simply add another if statement for that, same logic, but before the selected if statement, if the number is included in the usedNumbers prop that we just passed to this component, then make this function return used. We can test what we have so far. Our fake test data is showing up as used now. We can reset the testing data and start the usedNumbers state with an empty array. And now we're ready to implement the acceptAnswer function. This function is going to change a few things on the state, so it will be a simple State function call. In there, we first need to change the usedNumbers array to basically add to them the currently selected numbers, which we can read from the previous state here. Once we accept a list of selected numbers, we need to reset the selected numbers back to an empty array, and we should also reset the answerIsCorrect state, and at that point, the game is ready for a new random number of stars, so let's use the same expression to give the random number of stars a new value. The Button component needs access to this new acceptAnswer function because we want to click a success button to accept an answer. So we'll pass it here as a property. And within the Button component, the success button needs an onClick handler, within which we're going to invoke the acceptAnswer function that we now have on the props of this component. And we can test now. Select an 8 and 1, check, and hit the button one more time to accept, and now both 8 and 1 are marked used.
We have a way to check and accept an answer now; let's try to win this game. Select good answers and accept them, keep going, we reach a game over pretty quickly, there are no possible answers in this case for 2 stars. It's pretty hard to win this game as is, so I'm going to introduce a new feature of redrawing the number of stars. Let's add a new button under the main button, and inside that button let's use an icon of fa-refresh. Let's use a Bootstrap warning class for this button for the orange color, make it a smaller one, align those buttons center, add a couple of line breaks, and it looks good enough. So what should happen when we click this button? Let's wish there was a property on this component that we can call, with the name of redraw, and let's go up the chain and pass that property downward assigning it to a function on the Game component. And now we can define that function, all it needs is to change the state and generate a new random number of stars using the same expression, and it should also reset both the answerIsCorrect state and the selectedNumbers state if we have any. And now we can test. Select a number, hit redraw, and get a new random value for the number of stars. Alright, now that we have this feature, let's try to win this game one more time. Select all correct answers. Keep going, keep going, and we get to this state where it doesn't really have any answers, so now we can redraw, and we can keep redrawing until we hit a random number of stars that we can use, and win this game by selecting all numbers. However, the game is now too easy, you can just keep redrawing and you'll eventually win, so we need a balance between no redraws and too many redraws. I'm going to test a limit of 5 on the redraws, so let's implement that. This would be something that we need to keep on the state, let's call it redraws, and I want to display it right there on the same orange button. So let's go ahead and de-structure it out of the state in the render method, pass it down to the Button component as a property. Within the Button component, we can display this new property on the new redraw button by reading it directly from the props object. Alright, looks good, but it doesn't work yet. What we need to do to make it work is, when we click that redraw and invoke the redraw function, we need to decrement the values of redraws, straightforward. Inside the redraw function when we're updating the state, we should also update the redraws, but since this is going to depend on the previous state now, we should switch this setState call to use a function argument, and use the prevState.redraws - 1 to decrement the current value of redraws. Now we can test, and it works. It won't stop the action though, if we have 0 redraws, we should not be able to redraw any more. so let's write an if statement in the redraw function to guard against that. When the redraws are 0, do nothing. Now if we test, 0 redraws is where the button stops. This redraw button should be disabled when it reaches 0, the player shouldn't be able to click it. Implementing that is easy. In the Button component and on the redraw button element, we can add a disabled property, and make its value true when the redraws are 0, and if we test now, when we have 0 redraws we cannot click that button.
The Done Status
Winning and Losing the Game
Now that we have a doneStatus and we're displaying it whenever it changes, we now need to update it according to the games rules. Let's create an updateDoneStatus function on the Game component. Inside this function we'll need two conditions, one for a successful run, and one for the game over case. This updateDoneStatus function will conditionally update the state based on the previous state, so we'll need a setState call with a function argument syntax, and inside the function we'll write our two conditions. The successful run condition is easy. If at any moment we have exactly nine used numbers, for this case, we'll just update the doneStatus to indicate so. If that was not true, then we should check if the game is over, and the game is over if there are no more possible solutions. Assuming we have such a function, I am going to just use it here, and for that case, we're going to update the doneStatus with the Game Over message. We should only do this check though if we don't have any redraws left, because if we do have redraws left, then it doesn't matter if we don't have any possible solutions, we can still redraw and continue. Alright, let's now implement this possibleSolutions function. This function also needs access to the information on the state, but instead of reading the state directly, since we're inside the function argument of a setState call, we should use this prevState variable to read the state properly. We'll just pass it to the possibleSolutions function. So possibleSolutions will receive a state object and use that directly. This function actually only needs two things from the state to operate, so let's just de-structure them here in the argument. We need the current random number of stars and the array of usedNumbers. We need the array of usedNumbers to calculate what numbers are left that are available for a solution; I'll put those in a possibleNumbers array, it's the original range of numbers from 1 to 9 filtered out to remove all currently used numbers. So if the number is in the usedNumbers array, we want to exclude it here. So now we have a list of possible numbers in an array, and we have the value of the random number of stars, and what we want to answer here is the following question. Does the array of possible numbers have any combination of numbers that sum up to equal the value of the stars? This is a popular array logic problem which has some obvious cases, but the general approach is to calculate all the combinations in the array, and loop over them checking their sum against the value of the stars. The exact solution to this problem is a bit involved and it's beyond the scope of this course, so I did prepare a version of it and I'm just going to use that here. This will be a general function that receives an array of numbers, and a number to compute the possible combination sums for from the first argument array of numbers. To get this function, I saved it under bit.ly/s-pcs, possible combination sum, just copy the function as is and include it in the editor. Read over this function and try to understand what it's doing, there are a few advanced techniques here, but it's always fun to decipher. So now that we have this updateDoneStatus function, we need to invoke it, and we should do that in two places, whenever we accept an answer, and whenever we update the redraws. These actions can lead to either a game over or a game success. However, we need to update the done status after we're done updating everything else in the state, because the updateDoneStatus function depends on the component state itself, but simply calling the updateDoneStatus function after this setState function call might not work correctly, because the setState call is an asynchronous call. So do not assume that multiple setState calls will do their thing in order. React gives us a second argument in the setState function, which is a callback function we can use to run any command when React is done updating the state. Doing it this way guarantees the order of the operations here. Since updateDoneStatus itself is a function, we can just pass the reference to it here directly, and we need to do that in both acceptAnswer and in redraw. Test and make sure we did not break anything, make sure you're not seeing any errors in the console, and let's try to get to a no possible solution state. Drain all the redraws first, and accept the few possible answers that you can, and there you go, 1 star has only 1 possible answer which we used already, so it's not available and we got a Game Over message. Let me try to win this game real quick. Don't redraw this time and try to play it smart, and when you have all the numbers used, you should get this Done message. When the user loses or wins this game, there is nothing we can do in the UI, so we should give the player a way to reset the whole game and play again. Let's add a button under the done status message, call it Play Again, give it some Bootstrap classes, and let's update the doneStatus property to force this button to show up so that we can see it. Let's assume that the DoneFrame component will receive a function reference as a prop that it will use in its onClick handler; I'll call this function resetGame and pass it down to the DoneFrame component from the Game component. Inside this function's definition on the Game component, what we want here is to simply reset the whole state of the game and start over, so we need to set the state to the same initial state that we used here, but to avoid duplication here, let's create a function on the class level, and make it return the initial state object. We can now use this function both for the actual initial state and for the resetGame setState call. And since this is just a one-liner function here, we can use the arrow function shorthand syntax like this. So testing that now, play the game, and when you get to either a win or lose state, the Play Again button will reset the whole game. I think we have a game in good shape now. There are a lot more features we can add, but I'll give you one challenge here to run with. Add a timeout for the game. For example, make it so that the user only has 1 minute to win the game, and it will be game over if they take longer than that. This particular challenge is important because it will make resetting the game a bit harder. I will write a follow up blog post about how I'd solve this challenge, and I'll publish the solution on edgecoders.com. I hope you've enjoyed this course. I'd love to hear feedback about what you liked and what you didn't so I can make the next course better. Have fun playing the game you just wrote with React.
We finished the most important features of the game in this module. We started by implementing a way for the user to check an answer, and accept it afterwards. These features allowed us at that point to officially play the game. However, it was too hard to win the game, so we introduced the feature of five redraws to balance the level of the game. The last component we wrote for this game was to display whether a user won or lost the game whenever they reached one of these states, but for that, we needed to calculate the state of no possible solutions. And we used that state to figure out the state of winning or losing the game, and we displayed that to the user. This course was an introduction to the basics of React.js; there is a lot more to learn. There are a lot of resources you can now use to continue learning React. I recommend checking the official documentation next. It has a lot of great resources, and it has gotten a lot better lately. I also recommend that you continue adding features for this game, try to at least add the play timer that I mentioned in the previous clip, but you can also think about managing points based on how fast you can win this game and how many redraws you've used. You can also get the player's information and when they win, store their points somewhere, and then create a leaderboard widget for all players. If you're planning to use React with REST APIs, you should take a look at GraphQL because I think it's a much better solution to working with data. And finally, if you're really serious about React, you should learn Node. Node is the platform you're going to be using to compile your React applications, and it's also the easiest option to render server-side HTML from the same React applications that you write for your clients. My Node.js course on Pluralsight is an advanced one, so if you're just starting with Node you should probably take a more introductory course first, but make sure that you get really comfortable with Node itself because that would make your React experience much more valuable.
Samer Buna is a polyglot coder with years of practical experience in designing, implementing, and testing software, including web and mobile applications development, API design, functional...
Updated12 Apr 2017