What do you want to learn?
Skip to main content
by Liam McLennan
React is one of the world's most popular libraries for creating web user interfaces. This course will teach you how to confidently construct React applications that are simple and easy to maintain.
Resume CourseBookmarkAdd to Channel
Table of contents
What Is React?
Demo: Setting up a React Development Environment
Advantages and Disadvantages
A React application is a composed set of components. Each component has the design represented in this diagram. The inputs to a component are properties referred to as props and state. The difference between the two is that state can change. As much as possible, you should design components that don't use state because they're wonderfully simple. Note that the flow of data is all in the same direction. From the model via the render function to the document object model. Props and state collectively represent the model. The document object model or DOM is the direct result of rendering that model. For a given model, the rendered document object model will always be the same. The way to change the document object model is to change the model. Once the document object model is rendered, it can generate events which feed back into the component state and trigger another render cycle. Conceptually, we can imagine that for any state change, React will regenerate the entire document object model but if React actually worked that way, the performance would be poor. Instead, React maintains it's own document obstruction. A component's render function updates this fake document objects model known as the Virtual DOM which is extremely fast. Once that happens, it is the job of the React framework to compare it's fake document object model to the current state of the real document object model and update the real document object model in the most efficient way possible.
React vs. Angular
A React Application or widget is built from a set of components. This module will introduce components and then create the foundation for the rest of the course. Every React Application has a single root component. Typically, that root component contains other components that in turn contain more components all organized into a tree. We will discuss what a component represents in a React Application. First, we must define the component then we can use the component by rendering that component into the DOM. We will discuss what a component's props are and what lifecycle events are available for our component. React components have a facility called State which we will look into before finishing with a brief look at some of the options available for testing components.
What Is a Component?
Components are the fundamental unit of a React Application. They are both simple and powerful. Each component corresponds to an element in the DOM. The component is responsible for rendering the content of that element and for handling any events that occur within it. Components can be nested inside other components. This is what is meant by composing components and it is a powerful technique for achieving reuse. Nested components correspond to nested DOM nodes. The outer component is said to own the inner component.
The Author Quiz
The Author Quiz is a sample app that we will build through this course. It's a simple game that displays the image of a famous author. The player has to select from a list the name of a book written by that author. In the example shown, we're looking at William Shakespeare so either Hamlet or Macbeth is the correct answer. If the player chooses an incorrect answer a red highlight is displayed. If the player chooses a correct answer a green highlight is displayed and the continue button appears. Clicking continue causes a fresh game to be loaded. Let's have a look at the completed application. This is the completed Author Quiz application that we will be working on throughout this course. When the page first loads it starts a game. This time it has randomly chosen a photo of Mark Twain. Did Mark Twain write The Shining? Apparently not but he did write The Adventures of Huckleberry Finn. A correct answer shows a green highlight and the continue button. If I choose continue I get a new game loaded.
Defining a Component
Rendering a Component
Bootstrapping the Author Quiz
Now that we have enough knowledge to be dangerous let's begin creating the Author Quiz application. First, we will use Create React App as shown in the last module to initialize and empty application skeleton. Next we will add the Bootstrap CSS framework to help set some basic styles and to provide support for responsive design. Finally, we will define our top-level AuthorQuiz component with some placeholder content. There is a lot more to follow before the application is complete but this is enough to get us started on our way and to build a strong starting point that actually runs. To begin our Author Quiz sample application we use create-react-app to create a new application called authorquiz. We can now follow the instructions and change into the newly created authorquiz directory and then start our application with npm start. And this will just verify that the installation was successful and everything is working properly. Okay, great. There it is, our new application up and running. Now the next thing I'd like to do is add Bootstrap for some basic CSS styling support. So at getbootstrap.com we can read all about the Bootstrap CSS framework. And on the homepage if we scroll down, in the installation section there's an option to use the Bootstrap CDN and it includes a URL to the Bootstrap CSS. So I've highlighted that URL, I'll copy it to the clipboard and go back to my terminal. I stop our application for the moment. Once I have that URL I can paste it into my browser. And then save that CSS file into the source directory of my application. Now that we have the Bootstrap CSS file local in our application we need to include it into our application and the build process so that those styles apply. I've opened the Author Quiz application in Visual Studio Code. You can see the files that make up my application on the right hand side. By expanding these source directory I can find the source to App.js and this is the main component of our application. You can see already that it has an import for a CSS file, App.css. I'll leave that one there and I'll add a second import this time for the Bootstrap CSS file that we just downloaded. And now switching back to the running application I can see that the Bootstrap styles have been applied. If I open the developer tools in my browser, have a look in the style editor, the third one is the Bootstrap style sheet. So, confirming that it's definitely being loaded and used. Because we're building an Author Quiz application I'd like to rename some of these components. So, I'll rename App.js to AuthorQuiz.js. The corresponding test file becomes AuthorQuiz.test.js. And where the app component is used in index.js. I changed that reference to AuthorQuiz as well and also changed the import. Within the Author Quiz module I'll rename the app component to AuthorQuiz and make that the default export. And finally, I would like to remove the content of the AuthorQuiz component, replace it with a div containing the word Author Quiz. Let's jump over to the browser and make sure that everything is still working, and it is. Okay, so our app has reloaded and now all it contains is a div with the words Author Quiz. It's not much but it's a start that we can build on through the rest of this course.
React class components allow you to override lifecycle methods if you need to perform operations at particular times in a component's lifecycle such as when it has just been created or immediately after it has been inserted into the DOM. Methods with will immediately before the verb are called immediately prior to that verb happening. For example, componentWillMount is called immediately prior to the component mounting. Methods with did immediately prior to the verb are called immediately after that verb happens. For example, componentDidMount. Component lifecycle methods are useful when you are implementing a React component to wrap an imperative API. For example, you may be creating a React component for a jQuery plugin. You would use the component lifecycle methods to initialize the jQuery plugin and possibly to remove it when it is no longer required. This slide shows some of the lifecycle hooks that are available during the mounting and updating phases of a component's lifecycle. Check the React documentation for the complete list.
In addition to props React components have another way of holding data called State. Props are values passed in by a component's parent. Its state is local, mutable data that can be created and modified within the component. Having local data that can change within a component increases complexity and limits the composability of the component. As such, you should avoid using state when possible which is nearly always. In module one we saw a React component that counted the number of times it has been clicked. If we wanted that component to be self-contained we could move the click count into the component state. To use State we have to use a class component. In the class constructor we initialize the component state to a sensible default. In this case an object with a clicks property with the value zero. When we display the click count we read the value from the state property not from props because clicks is an internal state value, not an external props value. In the onClick handler we use the setState function to set the state to a new value. It's important to use the setState method rather than updating the state directly so that React knows that the state has changed and the component should be re-rendered. It's usually better to store and manage application state centrally so avoid class components and state whenever possible. This is the ClickCounter component implemented with local state. As we saw in the slides we have a constructor for the class which initializes the clicks to zero. When the component is rendered it outputs some text, this div has been clicked, followed by the clicks value from the local state. I test it out, say each time that I click on the component the number of clicks is incremented. And that's happening via the onClick handler on this div and every time there's a click event we handle that event, we call setState and we set the state to whatever the clicks value was before plus one.
The setState method merges the new state with the old state. All of the previous state remains unless it is overwritten. Consider a state object with properties a and b having values one and two respectively. Calling setState with the properties b equals three and c equals four produces a new state object. The property a is unchanged. The property b is overwritten by the new state and property c is newly added. In addition to changing the state of the component setState also causes the component to be re-rendered eventually. For performance reasons setState calls a batched. There is no guarantee that the state change will occur immediately.
Testing should focus on application logic. We test that our applications are correct according to their requirements. React is a user interface technology. An application's logic should not be implemented in its user interface, and therefore, React components should generally not contain application logic and are not a high value testing target. Having said that, it can be useful to test React components and there are properties of React components that make them amenable to testing. In particular, the function components that we favor are a simple function from props to JSX. They're easy to test by rendering the function with a set of props and making assertions about the result. We can also simulate events like the user clicking a button and make assertions about what happens. Let's try adding test to one of our example components. I have added a file called Hello.test.js to a Create React App application. Because this is a Create React App application it's already setup to be able to run test. Create React App uses Jest as the testing framework which is another Facebook project. If you search the web for Jest you'll be able to find this website which explains how to get set up with Jest and documents, how to write test with Jest, and including all of the different types of assertions that you can use. But as I said, Jest is already setup with Create React App. To be able to demonstrate how the testing framework works I've created one simple test that I expect to fail. You can read more about Jest but at a very high level tests are written using this it function where we make some kind of assertion about what we're expecting, and then implement that in code. If you like you can group those tests by wrapping them in a describe function call. Describe is where I say I'm going to have a set of tests that are about setting up testing. And then the particular test that I've created is that it should fail if I expect one plus one to be three. I can run this test using npm test. And you can see that I have one test failed, one out of one because it expected three to be two. I always like to set up my test framework by testing a failing test case because that shows me that the system's working. If I was to start with a test that I expect to pass, then when the test run everything passes and I get less information about whether or not things are actually set up properly. So now let's make this more interesting and see how we can test a React component. Earlier we used a component called Hello. It's a simple React function component that renders some content including a passed in timestamp. Let's begin our testing efforts with the simplest possible setup which I'll call when testing directly. And by this I mean that we're going to test our Hello component simply as a function without any particular helpers for React. Because it is a React component we do need to import the React library so that the React elements will be in scope. We have to write a number of tests and before each of the runs I want to run a setup function so I used the beforeAll helper, and within this helper I want to call my Hello component and pass in a props object. And the props object contains a timestamp as an ISO string. Now I can create my first test. I'd like to start with something simple so we will test that the Hello component returns a value. I make an assertion on the result and I'm simply going to say that I expect the result not to be null, does the result have a value? Okay and that test is passing. Now to get slightly more sophisticated we can assert that the result returned from the Hello component is a h1 element. To do that I look at the type of the element that's returned. I expect it to be a h1. Every time I save the tests are rerun and now I have two passing tests when testing directly return a value and is a h1, both passing. Moving along, I can test that element has children. To do that I look at the props of the result and the children property. And I say that I expect that to be truthy. So those are some simple tests that I can run just by testing my React function component as a function. Next I'll show how we can test a component with ReactDOM. And ReactDOM you'll remember is the module that we use to render a React component into a webpage. And I would like to test that it renders without crashing. To do this I need to create a DOM element which I can do using document.createElement. I'll create a div then I can use ReactDOM to render a Hello element and I render it into the div that I just created. When I did that I got an error. When I look at the detail of the error I can see that it's failing because ReactDOM is not defined which means that I've forgotten to import a module. If I add the missing import then all of my tests are passing. To do more sophisticated testing of React components there's a helpful library created by Airbnb called Enzyme. So the next thing I'd like to do is add Enzyme. To add Enzyme to my project I install the Enzyme module as well as the Enzyme adapter for React 16 because that's the version of React that I'm using. Now that that's done I can add another set of tests. When testing with Enzyme we will use a helper called shallow from the Enzyme module which is used to perform a shallow rendering of a React component. Once again, I'd like to test that the Hello component renders a h1 element. So, using the shallow function can pass in a React element to perform a shallow render and the result return from shallow is an object containing a number of helpful methods that I can use. In this case I'm using the find method to search for a h1. And I expect the length of the collection produced to be one. Once again my test have failed because I've forgotten to do something. When I look at the error message I'm told that Enzyme expects an adapter to be configured. You might remember that we did install an adapter but then I've forgotten to configure it in the code. Fortunately this is easily fixed. Before I run my test I simply have to call the Enzyme.configure method and configure the adapter and that adapter also needs to be imported. And finally, I need to include the default export from Enzyme as well and now my tests are running. Finally, one last test I can use Enzyme to test that the content of my component is what I expect to be. So I would like to test that the result contains the string Hello at a particular timestamp. Once again, I shallow render the Hello element and then make an assertion that the result that I rendered contains an h1 element containing the text Hello at that particular timestamp, and that should be true. So that's just a quick introduction of some of the techniques that you can use to test React components. Now that we know how to test React components let's add some tests to our Author Quiz application. As I mentioned before, any file containing something.test.js will be detected by Jest and run as a collection of tests. So I've added a file, AuthorQuiz.test.js which we will use to test the AuthorQuiz component. So I start by introducing this test library as a collection of tests for the Author Quiz. As before, we'll start with an easy one and just test that our component can render without crashing. We'll use ReactDOM and the render method to render an instance or element of the AuthorQuiz component and that test runs successfully. Jumping over to the AuthorQuiz component now. We can see that it doesn't do very much so there's not a lot for us to actually test yet.
React applications are constructed as a hierarchical graph of connected components. Components can be defined using the function syntax or the class syntax. Prefer the function syntax. It is neater, more concise, simpler and helps to discourage bad practices. Components can render based on data from two sources, props and state. Props contain immutable data passed from parent components. State contains local mutable data. Avoid using state where possible. Props can be validated at runtime using the PropTypes library or they can be validated at compile time using a static type checker like TypeScript or Flow.
What Is JSX?
Not Using JSX
Props in JSX
ES 2015 introduced a feature called Spread syntax that allows arrays and objects to be expanded in place by prefixing the value with three dots. With spread syntax, an array or object can be used when multiple values are expected. JSX supports spread syntax, so that a number of props can be specified by using the spread syntax on a single object. The sum component expects two props, A and B instead of specifying A and B separately we can use the spread syntax on an object containing properties A and B. The two forms are equivalent. Sometimes the spread attribute JSX syntax is more convenient, especially when there are a large number of props involved. Here we have our familiar sum component and it remains unchanged. The difference in this example is that I've introduced a new props object and set my values A and B on that object. Then when I create the sum element, I use the spread syntax to expand the props object and supply the expected props A and B.
So far, we have seen props used as a way to pass data into a component. Props are also the preferred way to pass data back out of the component. This usually happens in response to an event. To use props to pass data out of the component, specify a function as the prop and call that function. Pass the data as the function arguments, the clicker component has a single prop handleClick, which is a function. It renders three buttons, A, B and C. Each button has a click handler setup to call the handleClick prop. When the application is rendered, the handleClick prop is defined as a function that receives a letter and logs that letter to the console. So when I click A, the output is A clicked, B B clicked, C C clicked. So this is an example of the way in which we can pass a function in as a prop and then call that function as a way of getting data back out of the component.
React Data Flow
As we know, react applications consist of react components arranged in a hierarchy or directed acyclic graph. Data is passed down the component hierarchy by passing values into components props, data is passed back up the component hierarchy by passing values as function arguments to functions passed in this props. ClickyButtons is a react component with two props. Number of buttons is a value passed into the component that tells it how many buttons to render. OnSelection is a function passed into the component that is called when a button is selected and that is our mechanism for passing data back out of the components. The ClickyButtons component renders a div. Inside the div, I create a range from one to number the of buttons plus one and for each of those numbers, I call the make button function. And the make button function creates a react element when that element is clicked, the on selection prop is called and the ID of the button is passed as the function argument. So when I click the button 39, 39 is passed to on selection which is console.log and so 39 is logged to the console.
JSX and HTML
One of the nice properties of react is that it escapes all content by default, making it harder to fall victim to cross site scripting attacks. But sometimes you weigh the risks and decide that you have a valid reason to include unescaped content. Usually because you want to show somebody some user created content. Unescaped content means content that is not encoded to prevent cross site scripting attacks. Cross site scripting attacks are a major security vulnerability which is why the attribute for directly setting HTML from code is called dangerously set inner HTML, and it requires an object with an underscore underscore HTML property. You should avoid using this unless absolutely necessary. This is an example that shows the default behavior of react. The dangerous container component populates the content of a paragraph tag with a string of HTML. Because of react's default behavior to encode output, what we end up seeing is the actual HTML. This is safe, but may not be what we want. For example, what if I want the content to show as the word hello bolded? In that case, I can use the dangerously set inner HTML property. Instead of the usual react syntax, I provide an object with an underscore underscore HTML property, set to the content and I provide that to the dangerously set inner HTML property. This tells react that that content should not be escaped and in turn that means that the output is correctly formatted.
Child Expressions and Elements
JSX is hierarchical. So any valid JSX expression may be a child of any other JSX expression. You can have many JSX expressions or elements within a single JSX component. That is a JSX component may have zero, one or more direct children. This is necessary since JSX needs to be able to represent HTML. Without multiple child elements there would be no way to represent multiple items within a list or multiple rows in a table. Within a component, you can reference its child elements via the special property children. This means we can create components designed to wrap other components. For example, we might define a component that controls the visibility of its children. It doesn't matter what the children are, anything with in this component can be hidden and shown. Conditional display is a react component that shows or hides whatever child components are passed to it. It expects a prop value is visible that is a Boolean and is required. Conditional display displays its children if the isVisible prop has the value true. When we use the conditional display component, the child elements can be anything we want including nothing, one component, more than one component and components with their own nested children. Here we are providing two child elements to the conditional display element. When the isVisible prop is true, these elements will be shown. When is visible is false, these elements will not be shown. This example includes the sum component that we have seen a number of times before. And the conditional display component that we saw in the slides. When the react application is rendered, isVisible is set to the value of the show sum property of the state object. The children of the conditional display element are a H1 heading tag and a sum element. Finally, I have defined an interval and a callback that fires every two seconds, toggles the U value of show sum. So it'll be true for two seconds then falls for two seconds, then true for two seconds and so on. And also calls the render function to re-render the application. And as you can see, that has the effect of displaying the child elements for two seconds and hiding them for two seconds and toggling back and forth.
The Author Quiz
Could you create a React application that makes no use of events? Sure! You could have a read only application. In this scenario react would function as an over complicated view templating engine. Comparable to Handlebars EJS, Razor ERB or Hammel. React is at its best in highly interactive user interfaces and that means dealing with events. Most developers will have worked with events before. However for completeness we will briefly discuss what events are and why we should care. The events generated by the browser DOM we will call DOM events. This includes things like button clicks, scrolling and change events. When we create our own components we can give them their own events. We will call these component events.
In software development, events are an extraction representing something happening at a particular moment in time. An event is a value carrying data describing itself. An event could be a user action like a mouse click or a character types. It could be a piece of data arriving from the external system or a timing event. Event-driven programming is a programming paradigm in which the flow of control is driven by responding to events. User interface programming has a tendency to rely heavily on events since the program is interacting with a human and needs to respond to the users actions. Typically, the programmer registers functions to be called when a particular event occurs. Those call back functions then respond appropriately.
DOM Events are the events generated by the browser. Things like button click events, change events for text inputs and form submission events. All browsers provide an event based programming model. They all provide a similar set of events that behave in a similar way. So that the programmer does not have to understand and account for the specific quirks for each browser, React provides a normalized event abstraction called SyntheticEvents. DOM Events are the events that receive a react SyntheticEvents object. Events is a simple react component that renders a button. An event handler caller click handler is registered to be called when the buttons click events fires. Click handler receives a SyntheticEvents parameter and logs it to the console. Note the click handler is a function that takes a parameter and sends it to the console. Console.log is also a function that takes a parameter and sends it to the console. So we can simplify this code. Anytime you have a function that just forwards parameters to another function you can replace the outer function with the inner function. Here we have the events component from the slides. The events component contains a button and an event handler is registered for the onClick DOM event. When the onClick event occurs, click handler will be called. It will be passed a SyntheticEvent object and a SyntheticEvents objects will be logged to the console. Let's try it. When I click the button the event object is logged to the console. So that's an example of a reactive function component that handles a DOM event.
When an event occurs it may be desirable to handle the event and prevent the browser from doing what it would normally do. The SyntheticEvent object has a prevent default method for this purpose. The most common example of this is preventing forms from submitting. The default behavior of a form is to submit the page and reload. In a single page application this is usually undesirable so we handle the form submission, cancel the browsers default behavior using the preventDefault method and submit the data as an A jacked request that does not result in the page reloading. Another example as shown here is the check box input. When clicked the browsers default behavior is the check the check box. This codes calls preventDefault which will stop the check box from being checked when it is clicked. This is the nocheckbox react component. I called it nocheckbox because unlike a check box this one can't be checked. Watch what happens when I click the check box. As you can see I click the check box but the check box is not checked. The reason why is because of what happens in the event handler we've registered for the onClick DOM event. Here is that event handler and what it does is call the preventDefault method on the SyntheticEvents objects. That cancels the browsers default behavior of checking the check box. Let's have a look at another example. This one is a Java script class style react component called reloader. Let's start by looking at the render method and we can see that the reloaded component brings a form. The form has an event handler for the on submit DOM event. It also has an event handler for the on changer events of its text input which is bound to the content property of the components state. And finally there's a submit button for submitting the form. The onChar event handler that gets called each time the user types a character into the text box, updates the component state with the current content of the input. The event handler that's registered for the form submission checks what the state of the content currently is and if it's not equal to reload then it cancels the default behavior for this event. Now the default behavior for form submission is that the page will reload. In effect this means that we have a text box that we can type into. When we submit the form, under normal circumstances the default behavior should be prevented and nothing should happen, but if the content of the text box is reload, then the default behavior won't be granted and the browser will reload. Let's try that. I'll submit the form and you can see that nothing happens. The page is not reloaded because the default behavior is prevented. This time I set the value of the value of the input to string reload. When I click the button, the page is reloaded.
In addition to the browser events published by the react DOM components, components can publish domain specific component events. These events are typically at a higher abstraction than the browser events. Instead of events like button clicked, component events are likely to be things like task added or priority changed. In other words they express concepts from your application into main instead of raw events from the browser. In practice a component event is implemented by passing a function as a prop into a component and then calling that function when you want to trigger the event. Component events are the fundamental technique of passing data out of a component. For example, you may have a component that includes a form and the component may publish a component event when the form is submitted that includes the form data. Let's say that we want a react component that omits an event when it's clicked an even number of times. We will create a component called EvenCounter. The EvenClick event is a component event. As promised here is the EvenCounter component. Once again I'm using a Java Script class style component and the reason is that I want the component to have local state. The local state for this component has one property called clicks which initially has the value zero. This property is used to count the number of times the component has been clicked. The component renders a div with an event handler I registered for the onClick event. Inside the div we render a string explaining the number of times the div has been clicked. The function that handles the click calculates a new value for the number of times the component has been clicked. It updates the state to that value then it performs a check to see if the number of times the component has been clicked is divisible by two using the modular operator. If the number of clicks is an even number then we call the on EvenClick prop which is a function and we pass to that function the total number of times the component has been clicked. Where we create an element fourth component we must provide a handler for the on EvenClick event, and within that handler we take the number of clicks and we write a log to the console. Even followed by the number of clicks. For this demo I've got the browser tools to the right so you can see a little bit more easily now. If I click the even counter components note that the value the number of times it has been clicked increments each time I click. Now if you switch your focus over to the browser console each time I click if the total number of clicks is odd then nothing is written into the console. If the total number of clicks is even, then that value is written to the console. So this component is demonstrating both a DOM event in the onClick event of the div as well as a component event in the on EvenClick event of our EvenCounter component.
The Author Quiz
With our understanding of DOM events and component events, we can now make the Author Quiz game interactive. When the player selects an answer we want the user interface to indicate if they were correct or not. To do this we will check the answer the player selected for correctness. If it is correct then we will change the game background color to green. If it is incorrect then we will change the game background color to red. I've opened the Author Quiz application in Visual Studio Code and this is the AuthorQuiz.js file. We're looking at the turn components which is the main component that renders the body of our game. In its outer div, we've got a style where we set the background color to white. So this is where I'd like to be able to change the background color to green or red depending upon whether the user selected a correct answer or an incorrect answer. So I need to know if we're in a state where the user has selected a correct answer, the under has selected an incorrect answer, or the user hasn't selected any answer at all. And to do that let's add another prop into our components and we'll call this prop highlight and highlight will be a string that can take one of three values. None, correct or wrong. But now the problem is that we need to get to a background color and we're starting from a value indicating if the user has got the correct answer or not. So there's a mapping that needs to occur and I'll create a function to do that. The function, we'll call it highlight to background color because that's the mapping that I'd like to perform. To make this mass into claritive I'll define a value called mapping which is a Java Script object that maps values of the highlight prop to the corresponding background color. So if highlight is none then the background can be the empty string. If highlight is correct then we want the background color to be green and if highlight is wrong then we want the background color to be red and then finally to make that function work we return the correct value for the highlight. Now instead of hard coding the background color to white, we'll call our new function passing in the highlight value. With that done our turn component is now expecting an extra prop. Turn is used within the Author Quiz component. So here I have to provide a highlight prop and it'll add highlight as a prop on the Author Quiz component as well. So that's just passing straight through Author Quiz. Going up one more level where the Author Quiz component is used, we can add a highlight value to our application state and let's see what happens if I set the highlight value to wrong. I've opened the console and now I'll start the application. You can see that background of the application has changed to red. If I set highlight to correct, then the background of the application changes to green and if highlight is the empty string the background of the application resets to white. So the read side of highlight seems to be working fine. If we pass in a value to our Author Quiz component then the background color is set correctly. But what we haven't managed to do is respond to the users interaction with our game. And the way that the user interacts with our game is by clicking on one of the potential answers. We'll start by adding a onClick event handler to our book components. Each of these is a book component. We'll add another prop to book which we onClick, onClick of the function. When a user clicks the div containing the book title we will call the onClick prop and we will pass title of the book. Because we've added a onClick prop to book, we need to see where we're using the book component and make sure that we're supplying that prop. And this is where we'll take the opportunity to convert what is essentially a DOM event onClick into a component event. And you may recall that that is an event that's expressed in the language of our domain model. So instead of onClick, I'm going to call the function onAnswerSelected because the point of view of our application domain, that what's happening. At the low level it may be a click on a div but conceptually the user is selecting an answer to the quiz. An answer selected will be a prop of the term components. Which means that it must be provided by the Author Quiz components. Once again, the same as we did for highlights we'll simply pass the onAnswerSelected option straight through the Author Quiz which means that it's added as a prop of the Author Quiz onAnswerSelected. Looking at where our Author Quiz is used, we can finally provide an implementation for onAnswerSelected. So now we need to stop and think about what we want to have happen when the user selects and answer to the quiz. Well the first thing is we need to work out if their answer is correct or incorrect. So let's create a value called isCorrect and to assess that I need to inspect the turn data books collection and see if I can find one book in that collection such that the title of the book is equal to the answer the user selected. So we're looking at all the books belonging to the author displayed and trying to see if one of the books is the book selected by the user. And if that's true then they've selected the correct answer or a correct answer. Based on that correctness value it sets the highlight. If their answer was correct, then we'll set it to correct. If their answer's incorrect then we'll set it to wrong. We've handled the DOM event, we've handled the component event, we've updated our application state. But none of that will have any effect on the UI because the application's not being updated with that new state. And ultimately what needs to happen is that we need to tell React that the application needs to be rendered again. The way that I'm going to do that is that I'll put the rendering into a function. The very imaginatively titled render. Need to make sure that render is called when the script is executed but also, we'll call the render function after we've updated the application state so that that state change will flow through our user interface. Let's try that now. Okay so I'll select an answer. I selected The Shining which was not written by the author shown. So my answer was incorrect and the application has it highlighted in red. Now if I select Heart of Darkness, Heart of Darkness was written by Joseph Conrad who is shown here. So the answer is correct and the application highlights in green. Going back to our turn component, its props object has become quite complicated. And the correct functioning of our application depends upon those props being supplied correctly. So this is a good opportunity to add some prop type validation. And the way we do that is by adding a prop types property to our turn function. Prop types is an object which has a key for each prop. First prop is author and author is an object. So I'll use the prop types shape validator and this lets me specify what that author object should look like. It should have a name. A name is a string and it's a required value. ImageURL is also a required string as is imageSource. The books property of the author object is an array so we use the array of validator and it's an array of strings and that array is required. Other props of our term components include books. Same again. We have the onAnswer selected function which we can validate with the PropTypes.func validator. We also have the highlight prop which is a required string. Adding that prop type validation is giving me an error which says prop types is not find and that's because the prop types value has been moved out of the core react. So if we want to use it we have to import it separately. But often in my react import I'll add a second import for PropTypes and they come from the PropTypes module. Now the application is running again and working.
Testing the Author Quiz
When we last tried to test our Author Quiz application, we weren't able to get very far because the application didn't do very much. However we've since added more complicated rendering and some interactivity in terms of being able to handle the users input when they click on a book and make the user interface update accordingly. So now's a good time to see if we can uncover some of that functionality with our test. To be able to test components easily we will once again restore enzyme. Now we can add some imports for enzyme, we'll import the adapter for React 16 and configure the adapter. So that is essentially connecting enzyme to the enzyme React adapter. With that in place we can start our testing and our existing test is actually broken. The AuthorQuiz.test.js. Suite. Click to failure. We can see that the failure is in the prop types. The turn component expects a books prop because it's marked as required but it's not being provided. And this shows some of the benefits of adding the prop type validation. It's still a run time error but now we're get a much more helpful error message. The Author Quiz component expects our application state to be provided which we're not currently doing. So, I'll add a value called state that has some dummy application state data in it. We've got a turn data, we've got the highlight value. So these are all the things that are required for the Author Quiz components and then we just need to provide that to all of the quiz elements. And Author Quiz also expects the onAnswered selected prop. And for the moment I'll just provide an empty function. With that done we're back to a successful test suite and well placed to start adding some more advanced tests. So within the Author Quiz test, I'll describe a more refined state which is when no answer has been selected. To set up the application to test this state, I use enzymes mount function to render an Author Quiz. Our scenario is that no answer has been selected so to go to this state check highlight is set to done. So we're already in the correct state and now we can add assertions. I would like to say that when no answer has been selected, it should have no background color. Using the wrapper that we rendered the component into, we can use its find method to find the turn div via ACSS selector. Grab its props, inspect the style prop and the background color property on the style and we expect that to be the empty string. A number of tests has gone up by one and all test are successful. If we wanted to be certain that that's working properly we can change our expectation to be something that we know to be incorrect. Now the test fails and the test output is quite good within the Author Quiz suites and the states where no answer has been selected and the assertion should have no background color, we expected the value to be fuchsia but it was actually the empty string. And that's only going to do occasionally just to prove to myself that the test suite is working correctly and actually verifying something useful. We can go on and add other states now such as when the wrong answer has been selected. So this time because we want to test the state where the wrong answer has been selected, when rendering the Author Quiz elements you override the highlight value of the state to be wrong. And in that scenario our expectation is that the turn component would be rendered with a red background. So we add an expectation. It should have a red background color. The implementation is the same as before. We selected the turn div, grab out the background color from the props. Now this time we're asserting that it should be read and now we have nine passing tests. So that's working properly. For completeness then, I can copy and paste a whole group of tests and change the scenario to being when the correct answer has been selected the highlight value in that scenario is correct and the background color we should expect to be green. Now we have 10 positive tests. But what if we wanted to test an actual user interaction right down to the DOM click event level? Let's try writing a test for when the user selects their first answer. Once again we'll mount an Author Quiz component but this time I want to provide an implementation of the onAnswerSelected call back. So I'll create a value called handleAnswerSelected and SyntheticEvents the jest.fn method to create that function. Jest.fn creates a mock function. Now having mounted the Author Quiz component I can use the wrapper to find the answers which is the Dom elements with the DOM answer class. Select the first one and then simulate a click event on that helmet. Now we can add an assertion and I would like to assert that the onAnswerSelected component event is triggered. Another way to say that the onAnsweredSelected function should be called because handle answer selected is a special jest mock function we can use the expectation to have been caught. This means that the test will fail if our call back function has not been called. That test passes successfully. But, we can do even better than that. Because we don't just care that the function has been called, we also care about the value that's passed to that function. So we'll add another assertion. We choose that when the first answer is selected, the selected answer should be The Shining and that's because The Shining is the first book specified in our test data. This is another expectation against the handleAnswerSelected mock function and being a jest mock it allows us to use the two have been called with assertion to check that the argument passed to the function is what we expect it to be. The tests have run again and now we have 12 passed so that's all working correctly.
Events make applications fun. You could have an application without any events but it would be very, very boring. React provides two major features. A way of generating a user interface from a model and a system for processing events. So events are very important. The React way is simple and powerful.
Most web applications end up requiring some support for forms. It's easy for service side web applications because HTML has a form element, which supports serializing and posting form values. Client-side web applications are left to implement form support for themselves. React has some very basic form support built in. This includes binding values to the form, binding values back out of the form and accessing individual form elements. Notably absent is any built-in support for form validation. React supports the usual selection of form elements. They function much the same way as their HTML equivalents but with some subtle and important differences. The way that React handles UI rendering has an important incompatibility with user input. There are good and bad ways of working around it. If you don't mind giving up a little bit of fine-grained control, you can gain a lot of productivity and simplicity by using a form library to automate common form handling requirements. Where there are forms, there is validation. React doesn't provide any assistance with form validation. But it's not difficult to implement. Finally should all else fail, it's possible to bypass React's form element obstruction, and access the underlying DOM elements directly. Use with caution.
React allows the programmer to work with form elements such as the various inputs, text areas, and select lists using a syntax similar to html. When React renders a form input, it must preserve React's rendering semantics, which guarantees that the rendered UI is a direct translation of the user interface model. This has the effect of preventing user input. Obviously a bit of a problem for a form. We will see how React solves this problem later. But first let's have a look at some common form controls. The text input matches its html equivalent. The content of the input control is set using the value prop. The text area element is noteworthy, because it differs slightly from html. A HTML text area provides its content as text between the opening and closing tags. A React textarea uses a value prop just like the text input. Select is another element that is nicely matched to its HTML equivalent. A HTML select list is a select element containing option children. The selected option is set by providing a selected attribute to an option in the list. The React select element is similar except that the selected option is set by providing a value prop to the select element. The option with the value matching the value set on the select element is selected. This React component demonstrates the form inputs we've looked at so far. Firstly, we have a text input with the value set to react, which renders as we would expect. Similarly a text area with the value set to react and finally a select list. The select list has two options, Saturday and Sunday and the value is set on the select element to Sunday. And you can see that that is the option selected in the list.
Allowing User Input
The easy way to add validation to a form is to use one of the form libraries that has validation included. If you require complete control of your forms, and building them by hand then you will need to implement validation too. Users need to see what they type. So you'll still need to synchronize the form user interface with the model object is generated from. You then must decide when to trigger validation. You can validate on each change to the form. You can validate when the user moves between form fields or you can validate when the form is submitted. Once validation is complete, you must decide how to feed the results back to the user so they can correct any validation errors. It's common to show errors in line next to the field having the error or to show errors altogether for the form or some combination of both. What you choose is mostly about what will work for your user experience. And how much effort you want to put in.
Client-side Routing with HTML5 pushState
Adding Routes to the Author Quiz
I'd like to add a form to the Author Quiz but to get to a form I need some navigation and that means URLs and routing. But this is a client-side app. So how do we do routing? For the Author Quiz application, I will use the React router library to add some simple routing. We need a home route and I'd like to add a new feature to the application. The ability to add a new author. We will need a new route that will render a form allowing us to enter the data for a new author, which includes their name, the URL of an author image and a list of their books. I have opened the Author Quiz application in Visual Studio code. The first thing that I need to do to add routing to this application is to install React Router. And the way I do that with the terminal is npm install and the package is called react-router-dom. Everything that we need is in that package. While I'm waiting for that, I'll open index.js and this is sort of the route of our application. At the top of the file, we need to import the React Router library and we'll import two values, browser router and route. In the function that renders the application, we currently render an AuthorQuiz element. The first thing I'll do is create a new React component to render that element for us. And I'll just call that App because it's the top level of our application. It simply wraps the Author Quiz element. Now I can change this to app. I've made some significant changes so I'd like to just check that the application is still working and that looks fine. Now let's add some routing. So the first thing to do is wrap our components in browser router and that gives us the ability to introduce route components. So the first thing I'll do is add a route for the route path. I'll make this an exact route matching the route path and it should render the component app. If I've done that right, I should have preserved exactly the behavior that we already had because what I'm saying is that the router should render the app component in this position if the route path matches the route path exactly. And my application still works. The purpose of adding a router was to be able to add a second route. That second route will be a place where we can add a form that the user will use to add new authors to the application. So I'll create the second route. This time we'll match the path forward slash add and when that route path is matched, render a component called AddAuthorForm. That has broken the application. It no longer compiles because AddAuthorForm is not defined. It's correct because that is a component that we haven't created yet. So I'll add a new component AddAuthorForm. We have a header, Add Author, and for the content of this component, later we will add a form obviously for adding new authors. But for the time being we're just working on routing. So for the content I will just dump out the prop data that we get from the router and the router supplies a prop called match. We have another error. It says that the router may have only one child element. That's absolutely correct. So it's complaining that I've defined two routes as direct children of the browser router. So we need to wrap them together into a single parent. And this is where React fragments come in very handy. So it lets us solve that problem of grouping React elements under a single parent but using a component that has no DOM representation. So by the time the application is all rendered out, this React fragment component doesn't add anything to the DOM. If I'd used a Div or something of that nature, then obviously I would be adding another element to the DOM for no purpose other than to satisfy one of React's requirements. So the application looks like it's working. Let's try our new add URL and that's the routing working for us. So that's taken us to our Add Author form component. It's our Add Author header and then the content of the page simply being the route data that we get from React Router. It's telling us that the path is currently forward slash add and it's an exact match with no parameters. If I use the back button, it will go back to the main interface. I'd like the Add Author form to be discoverable. So let's add a link to this page. We need to go to the AuthorQuiz component and between the Continue button and the Footer I'll add a paragraph containing a link. So this is our first time using the React Router link component. So before that will work, we need to import that. So we import link from react-router-dom. The link component expects a to prop and this is where we specify the path. We provide the link text as a child. We'll say Add an author and then close the element. So now our AuthorQuiz application has this link add an author. When I click it, it navigates to the add URL. And that's all that we have to do to add routing to the AuthorQuiz using React Router.
Adding a Form to the Author Quiz
Because React is most useful for interactive web applications, they often involve forms. React itself includes well designed primitives for working with forms. But doesn't make any attempt to provide high level abstractions or productivity. If you need to do a lot of work with forms, it's worthwhile exploring the available React form libraries. You should be able to find increased productivity and consistency.
A State Container
Now that we are familiar with the model-view-intent architecture, we can look for ways to create abstractions that simplify the design. We already have React for the view component, converting the model into a user interface. A state container is a component that takes care of holding the model and controlling updates through the model. We will extend our stopwatch example to use a custom state container. The custom state container will implement three methods. GetState returns the current application state object held by the state container. Dispatch applies an intent to the application state, producing a new application state. Subscribe registers a call back to be called when the application state changes. That is when an intent is passed to the dispatch method. That's all we need for a simple state container. For this example, we will update our stopwatch to use a custom state container that we'll create in the process. The code I have here so far is from the previous examples. So we have an update function, and it's responsible for being able to apply intents to the application state to produce new application states. The only way in which it's changed from before is that the initial value of the application state is now specified as a default for the model parameter. Similarly, we have the familiar view function. It calculates how to display the current timer value, and it has the timer value and a start stop button as the user interface that's generated. I've defined a value called container, which is currently an empty object, and this is where we will implement our state container. Other than that, things are basically the same. So, what do we need our state container to be able to do. Well, in the render function, the view needs to convert our application state to a user interface. So we need to be able to get access to our application state, and I'd like to do that by calling container.getState. So that should return the current application state when it's called. That's one thing we need to be able to do. If we look in our timer that's firing once ever second, we need to be able to dispatch intents to our container. So I'd like to be able to say container.dispatch, and the intent, this one is tick. That should work. And similarly, in our view, when someone clicks on the stop start button, that's another location where we need to be able to dispatch intents to our state container. If the timer is running, then we dispatch the stop intent. If the timer is not running, then we dispatch the start intent. That's a good start, but we would still have an application that doesn't actually do anything. We need to close the loop and make sure that the user interface is rendered when the model changes. And to do that, I would like to be able to subscribe a callback function to be called when the model changes. And in this case, that function is render. Every time my application state changes, I want to call the render function to re-render the UI from the current model and update the DOM. So far we've worked from the outside in and we've come up with the API we want to use to work with our state container. What we haven't done is actually implemented the container. It's just an empty object. We need a way of building our state container, so I'll call a function called createStore. This is a function that doesn't exist to create our application state container or store, if you prefer that terminology. To be able to create that container, because it needs to apply intents to our model, that create store function is going to need the update function. So now we can implement createStore. The argument passed to createStore is our update function, and we'll call that reducer for reasons that will become obvious a little bit later on. CreateStore needs to return our application state container, which is an object. And as previously described, it needs to have a dispatch function that expects an intent. It needs to have a subscribe function that expects a call back, and it needs to have a getState function that returns the current state. Now we can implement those functions. I'll start with dispatch. When dispatch is called and an intent is passed. We need to call our update function which we've called reducer. We need to pass in the current state and we need to pass in the intent so we don't have that current state yet. So I'll call that internal state and define a variable with that identifier, internal state. Because the reducer returns the new state, then we need to assign that to internal state. So when somebody dispatches an intent, we set the internal state to be the new state produced by our update function. That also makes it trivial to implement our get state. That function can just return the internal state value. Subscribe is used to register callbacks that get invoked when the application state changes. So, when that function is called, we can simply push the handler onto an array of handlers. And once again, we need to define an identifier for that, and that can be initialized to the empty array. Now that we have that collection of handlers, when an intent is dispatched, after we've updated the internal state, we then need to invoke each of those handlers. So that's simply a matter of looping through the collection of handlers and invoking each one. That's all we need to do to implement our customs state container that has all of the features that we defined as being necessary. So, if we have a look at the output now, you can see that the application is being rendered, that's a good start. When I click the start button, the timer begins timing, and that is because the onClick event handler for the button is dispatching the start stop intents. Our custom state container processes those through our update function, the same thing is happening for the ticks that get dispatched once every second. The change in the model triggers the subscribe callback handler, which re-renders our application. And when we re-render the application, we pull the current application state from the container. So the functionality of the application hasn't changed, but now we're using a nice abstraction for the application store, which we've created ourselves from scratch.
As we have seen, it is not necessary to have a state container, and it is not difficult to build one. However, there are advantages to using a common state container library. It provides a degree of standardization. If many apps can use the same library, it is often less important what the standard is than that there is a standard. Using a popular library can also means that the implementation is likely to be more robust and have more features. In the world of React, Redux is a popular and quality state container. Most importantly, it provides a good basis for implementing the MVI architecture. Redux usage can be very simple, but it can also scale to sophisticated scenarios. Our custom state container used a function that converted the current state and an intent to a new updated state. In Redux, this function is called a reducer because it reduces the stream of intents to a single object, the application state at a moment in time. We have referred to the events that trigger state changes as intents, because that's what they're called in the context of the model-view-intent architecture. Redux refers to the same thing as actions. An intent and an action are the same thing. From now on, I will tend to refer to them as actions, because that is the term used in the Redux documentation. The Redux API will be familiar. I used the same API for our custom state container. CreateStore is the function used to create a new store, which is the container for our application state. To create a store, the programmer supplies a reducer function and the initial store state. Get state returns the current application state from within the store. Dispatch sends an action to the store to be applied to the current state. The action is processed by the reducer function which builds a new application state. Subscribe registers a call back to be called when the application state held within the store changes. To get the benefits of standardization and depending on a robust and proven solution, let's convert our stopwatch to use Redux, instead of our custom state container, remember that the two solutions have a similar API, so we should not have to change much. This is the stopwatch application as we last left it. It has a view update function. It's using our custom createStore to create our custom state container, which is assigned to the identifier container. In this code pen environment, I have added a reference to the Redux script to make it available. And now what I need to do to convert the application to use Redux, instead of our custom store, is I'll delete the createStore function, because we won't be needing that anymore. And where it was called, let's change that to Redux's createStore. Now our application has disappeared, it's not being rendered anymore, and that tells me that there might be a problem. So look in the Console, and here I can see an error that says that actions must be plain objects. This is a Redux convention that says that actions should be objects where I'm currently using strings. Further part of that convention is that the objects should have a property called type. Now this is optional, but it is the way that Redux is typically used. So everywhere that I'm publishing an action, instead of using a string, I need to change that into an object with a type property. That takes care of the button click. We also publish or dispatch an action on a timer, and now my application is back. In the update function, I will rename intent to action because that's the terminology that Redux uses. And now instead of switching on the intent, I'll switch on the type property of the action. And that's all I have to do to convert the application to using Redux. Because our customs state container used the same API with dispatch, getState, and subscribe. The only changes that were required were to use Redux's createStore function and to convert our actions to be objects, instead of strings.
React-redux is an extra module that helps with the integration of React and Redux. For sophisticated React and Redux users, it helps to make code neater and add some useful features. For beginners, it obscures the simplicity of React and Redux and make things more difficult to learn. Make sure you understand React and Redux on their own before you look to use React-redux to integrate them. The main service provided by React-redux is to connect React components to the application state. React-redux can provide data from the Redux store to components when the component is rendered, and it can provide a way for components to publish actions that can then be used to modify the Redux store. Provider is a React component provided by React-redux. When it is included in a React application, it enables all React components below it in the component tree, that is its children or children's children, et cetera to connect to the Redux store. Connect is a function provided by React-redux that enhances React components connecting them to the Redux store in the ways specified. To specify what data from the Redux store should be provided to the React component as props, connect expects a parameter called mapStateToProps. mapStateToProps is a function from the Redux store to a set of props for the component. mapStateToProps took care of getting data from the store to the component. Another parameter of the connect function, mapDispatchToProps, takes care of specifying how the component can send actions to the Redux store. mapDispatchtoProps is a function from Redux's dispatch function to a set of props for the component. In practice, this provides a place to map component events to Redux store actions. In the last demo, we converted our stopwatch application from a custom state container to a Redux state container. In this demo, we will further enhance the stopwatch application using React-redux to decouple our React components from our Redux store. This is the example as we previously left it. So it's using Redux.createStore to create the application store. In my code pen settings, I've added another external library so that we can use React-redux. The way that React-redux works is by wrapping a Redux component in a call to the connect function, and in that function call, specifying how the component should be connected to the Redux store. In our example, we have a view which is a function from a model to some JSX> by definition, view is therefore a React component. And since it's a React component, I can wrap it in the connect function from React-redux. And as described in the slides, this is my opportunity to provide two functions, mapStateToProps and MapDispatchtoProps. Note that connect doesn't actually take the React's component as an argument. The two arguments are mapStateToProps and mapDispatchtoProps, but connect returns another function, and that function takes the react component as an argument. Now because view is a React component and we're using connect to convert it into a new React component, I will use the convention of an uppercase name for the component and call it stopwatch. Now I need to define those two functions. We'll start with mapStateToProps. And as its name suggests, it's the function from state to a set of props. So that's a valid implementation. And for the other one, mapDispatchtoProps, one again, the name tells you what it does. We have to go from dispatch to an object containing some props for our component, so again returning an empty object as a valid implementation. Now we have to use our new stopwatch component, so let's go to where the application is rendered. Previously, we're calling the view function and passing in the application state. We'll get rid of that, and switch to our new stopwatch component. The way that React-redux connects components into the Redux store requires that those components be wrapped in a React-redux provider, so that the provider can make the Redux store available. So we'll do that, ReactRedux.provider, and we have to give a reference to the Redux store, which is called container. And now React-redux will take care of re-rendering our application when the state changes. So we can get rid of our render function. And also the subscription to the container, because React-redux is doing that for us. We just have to render the application the first time, and that's enough to get our application to render once again. When the stopwatch component renders, its data is supplied by the React-redux provider, mapping the store to the component via the mapStateToProps function. Currently, because we provide the empty object as the props, the stopwatch component isn't getting any props at all from which to render, and that's why the output is a bit odd. So here, instead of returning the empty object, we need to think about what transformation we need from the Redux store to props for our component. And in this particular application, the answer is no transformation at all. We simply want to pass the state straight through. More realistic examples will have more complicated application state. And only part of that state will be applicable to the stopwatch component. So we would use this function to extract out the pieces of state that we care about, but because this is a very simple example, the format of the application state is an exact match for the props that we need in the stopwatch component, so the function just returns the argument. Now when it comes to mapping dispatch to props, this is where we need to map our events that we want our component to be able to raise to dispatch and props. And there are essentially two events that come out of the stopwatch component that need to be able to modify the store, and those are starting and stopping which we will call onStart and onStop. So onStart becomes a prop that will be passed to the stopwatch component. We want it to be a function that dispatches an action. And onStart will of course dispatch the start action. onStop is very similar, but it will dispatch the stop action. So onStart and onStop then become props that are available inside of our stopwatch component. That means that in the click handler for the button, instead of dispatching actions onto the container directly, get rid of that, I'll also rename the argument to the component to be called props, just to make things a little clearer. And I can actually get rid of the handler function entirely, and just in the onClick binding for the button, switch on props.running. If the timer is running, then the event handler should be the onStop prop, otherwise the onStart prop. To get the application to work, there's one final small change we need to make, and that is in our update or reducer function. React-redux makes a performance optimization where before re-rendering the application, it firstly checks if the application state has changed at all, and it does that by a reference equality check. Because this update function mutates the existing model object, React-redux doesn't see that as a change to the application state, because it's still the same object. So what we need to do is make sure that reducer function always returns a new object if the application state has changed, and I can do that just by providing an empty object as the first parameter to Object.assign, and that has our application working perfectly. So what have we achieved? Well, in the definitely of our stopwatch component, which is this part, you note that we've managed to remove any references to the container or Redux. The only dependency of this component is props. Its only dependencies are things that are passed in as parameters. The mapping of those props to the Redux state is done via the React-redux connect function and the mapStateToProps and mapDispatchtoProps functions.
Author Quiz State Management
When we last left the Author Quiz, the way that it handled application state was very simple. The application state data was defined outside of the React components and passed down the component tree by props. When an event occurred, it was passed back up the component tree, layer by layer, to be handled and applied to the application state outside of the React components. By introducing Redux and React-redux to the Author Quiz application, we can add a standard and robust way of dealing with state changes. And we can prevent the tedious requirement of having to pass data up and down the component layers. Note that this is a trade-off. Whilst it makes development much nicer not having to pass all data through all component layers, it also breaks the reusability and composability of our components. As the application currently stands, I can extract any component at any layer of the component tree and reuse it elsewhere without modification. Once we switch to React-redux, we can only move our components if we also move an equivalent React-redux setup. The simple presentational UI widgets, you are often better off using pure React component by themselves to get the maximum reuse and composability. For application-specific UI components that require complex bidirectional interaction with application state, you will often be better off using the React-redux connected component style. Now we will add Redux and React-redux to the Author Quiz application. The first step is to install the NPM packages. And when that's finished, we can add some imports for Redux and React-redux. Now that we have Redux installed, we can use it to create an application state container or what Redux calls a store. Creating a store requires specifying a reducer function, so let's start off with the simplest possible reducer. Now the reducer is a function that takes the existing state and an action, and applies that action to the existing state to produce a new state. So the simplest possible implementation is simply to return the existing state. Now we have a Redux store, and we can use React-redux to connect to our React application. And the way that that is done is by wrapping any component that needs access to the store in a provider. And a provider has a reference to the store. The provider's job is to make that store available for all of its children and children's children. Just to try to keep the application working while we make the move to Redux, I'll leave the existing declaration of the state variable, and then we can slowly migrate to the new strategy. It's best always to keep your application working when you're doing a refactoring and working small steps and make sure that as you go, nothing is broken. So now we have a Redux store. It has no state in it and no way of mutating that state, and it's not used at all,, but at least it is made available to the components that make up our application now that the AuthorQuiz component is wrapped in a provider. So the next step is to modify the AuthorQuiz component to read its state from the Redux store instead of passing it in as a prop. To connect a React component to a Redux store, we use the connect function from React-redux. So I will important that and scroll down to the AuthorQuiz component, and wrap the AuthorQuiz component in a call to the connect function. Connect is a function with two arguments, mapStateToProps and mapDispatchtoProps. The call to connect returns another function, and the argument to that function is our original React component. What I need to do next is to implement these two functions, mapStateToProps and mapDispatchtoProps. Now their names tell you what they need to do. We need to map state to props. So the input to that function is the content of the current store. And we want to return the bits of that store that we need for the AuthorQuiz component, and that will be the turnData and the highlight value. The next function is mapDispatchtoProps. Again, the name of the function is instructive. It's a function from dispatch to another set of props. And this time what we're effectively doing is mapping vents that can come out of the AuthorQuiz components to actions that we want to publish to the Redux store. You can see that the AuthorQuiz currently has two events that it uses to pass data back out, onAnswerSelected and onContinue. So they will be the two props that we have to provide. onAnswerSelected, when an answer is selected, we will dispatch an action to the Redux store. By convention, actions are objects with a property called type. Now I'll call this action answer_selected, and I'll include the answer itself in that action. The other prop is on continue. This is the event that occurs when the user has selected the correct answer to the quiz and then click the continue button, in which case we will dispatch another action, and this is a continue action. Looking at the implementation of the AuthorQuiz component, we can say the props that it's expecting, turnData and highlight, will be provided by mapStateToProps. onAnswerSelected and onContinue will be provided by mapDispatchtoProps. There aren't any props left to be provided by the parent of the AuthorQuiz component, which means we can get rid of these here, and also we don't need to provide the state, since that is now coming from the Redux store via React-redux. Now that the AuthorQuiz is connected the Redux store, it's time to do a bit of work in that store, get some data into it, and get it handling the actions that we're publishing. The way to specify the starting state for our store is to provide a default value for the state parameter. We'll include the authors in the Redux store, the turnData, which we can set to the result of calling getTurnData and passing in the authors. And the other thing we need is the highlight value, which can start out as an empty string. That's our default value. That solves the problem of getting some data into our store, but we still need to make sure that we're processing the actions that the AuthorQuiz component is generating. Most commonly you will see reducers implemented as a switch statement, but we switch on the type of the action. Each different action type will be handled differently. First up, we have answer selected. This is the action that is dispatched when the user selects an answer. When that happens, we firstly need to calculate if they have selected the correct answer. This is something that previously happened in the onAnswerSelected function, so I can take the code from there. And the correct answer is when the author for the current game has written a book that matches the user's answer which is attached to the action. Having calculated if the answer was correct, we can now update the application state. We'll use object assign to make sure that we're creating a new object. We'll default to the existing state and then override the highlight property. If the user's answer is correct, then the highlight value is correct, otherwise the highlight value is wrong. The other action that AuthorQuiz publishes is continue. When the continue action is processed, we update the current state, resetting the highlight value to the empty string, and generating a new set of turnData by calling getTurnData again. Now I shall add a default case that will handle any actions that are not explicitly handled by a case in our switch statement. And in that case, we can just, once again, return the existing state. If we don't know how to handle the action, then we just don't handle it at all. We no longer need the state variable. We no longer need the onAnswerSelected function because we have the React-redux provider taking care of re-rendering the application. When the state changes, we no longer need the render function. We can simply call ReactDOM.render. The compiler is telling me that resetState is defined but never used. So I guess I don't need that either. And it looks like the application is still working, That's really completed the transition to Redux for the game part of the Author Quiz, but remember we also have the AddAuthorForm. So we'll look at that next. As with the AuthorQuiz component, the first step is to import the connect function from React-redux. Then when exporting the component, we use the connect function. Once again, we need to provide a mapStateToProps and a mapDispatchtoProps. The AddAuthorForm doesn't need to read anything from the Redux store. So, our mapStateToProps can be an empty function. It does produce some actions though, or at least one action. So we do need a mapDispatchtoProps function. And the event that we would like to map to a Redux action is onAddAuthor. When the form is submitted, we'll dispatch an action of type Add_Author, and that will include the author object. So this is going to happen when the user submits the AddAuthorForm. We'll dispatch the Add_Author action. In our reducer, we can process that action, add the new author into the application state. That's great. The problem we'll have is that the user is still sitting on the add author page. We need a way to make the browser navigate back to the main game interface on the root URL. And to that, we need to import the withRouter function from react-router. And the module for react-router is called react-router-dom. We can wrap our exported component one more time. This time with the withRouter function, and that will make sure that the AddAuthorForm component is provided with a history prop. In mapDispatchtoProps we can add a second argument for the components props. Then access the history prop and use it to navigate back to the root url which the router will pick up and render our game interface. Now we're dispatching the Add_Author action. We need to go back to our reducer and make sure that we're processing that action, which involves adding another case, this time for Add_Author. When Add_Author is dispatched, we will update the application state stored in our store, which will be equal to our existing application state, except that the authors array will be the existing array with the new author concatenated to it. Let's see if that still works. Okay, so, here I am on the main game interface. If I select Add an author, okay, that's interesting. The Add author route is broken. Previously we used an AuthorWrapper withRouter to make the history prop available. We've now move that inside the AddAuthorForm components. In fact, we've done that with the onAddAuthor event handler as well. So, that whole thing can go. In which case the AuthorWrapper is now superfluous and we can just delete that whole thing. Instead of using the AuthorWrapper, we can just use the AddAuthorForm component directly, and that's because it's now a component that's connected to the Redux store. The next problem that we're going to have is that AddAuthorForm is connected to the Redux store, but it's outside of the React-redux provider. So, it's not actually going to be able to access the store. To fix that, I can move the provider from the app component up one level. When I do that, we have a similar situation. The app component now wraps the AuthorQuiz component and doesn't add anything. So we can go. And this is nice. This is an example of the way in which things are being simplified by React-redux. Now our routes can map directly to the components themselves without the wrappers. And let's fix the navigation. So now it's time to try adding a new author and making sure that that still works. Let's add Jane Austen. And she's written some books such as Pride and Prejudice and Emma. And now we can add that author. And as before, then it's just a matter of playing the game until our new author is randomly selected, thus proving that they have been added to the application successfully. So this is the image that are used for Jane Austen. And in the list of possible answers, we have Pride and Prejudice which is a correct answer for that author. So that is working. The last thing I'd like to demonstrate is a little tool that can help you to debug issues with Redux and React-redux. You can see here that in my developer tools I have a tab called Redux. This is provided by a tool called Redux Dev Tools. Redux Dev Tools is available for Firefox and Chrome, and it provides a wealth of information about what's going on in your Redux application. You can see the current content of your store. You can see the actions that have been processed and what values they have. You can record and replay and just generally get a really good feel for what's going on with your Redux store. To get that working, you need to install the, the relevant extension for your browser, and then make a small change in the application itself. Where we create our Redux store, in addition to providing the reducer, we need to add a second parameter. Now if I go back to the application, it's reloaded and you can see that the Redux Dev Tools are showing a user interface. I can have a look at the State tab, and see the current value of my Redux store as all of the books and authors and the current turnData. The game is showing Shakespeare. And in the turnData we can see that the author is William Shakespeare. As we'd expect, if I select an incorrect answer, the highlight value changes to wrong. Over in the list of actions, we can see that there was an answer selected action. I can see that the answer selected action had the type answer selected and the answer Heart of Darkness. If I select the correct answer, we got another answer selected, this one is Hamlet. Continue button will cause the continue action to fire. All along the state is updating. If I was to switch to Add an author, fill out the form, you could see the add author action and the application state would have a new author in the authors array. So that's a very handy tool for debugging Redux and React-redux issues.
Liam is a software developer, technologist and product delivery expert. He thinks a lot about how to manage software delivery projects and how to grow high performance teams.
Released21 Jun 2018