React

Adding React to a Middleman project with Webpack

Middleman is a static site generator written in Ruby. It's a great way to produce rich static content sites without the need for a server. Despite this, it doesn't come with internal support for modern JavaScript frameworks like React. Luckily, Middleman 4 ships with a feature called External Pipeline which allows wiring in your own external build tool like Gulp or Webpack.

Let's look at how to integrate Webpack with Middleman for the purpose of using React on our site.

Install development dependencies

First, let's install the dependencies we'll need to build our React code. Note that we're using the --save-dev flag to indicate to npm that we should append these libraries to the devDependencies section of our package.json.

$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react webpack uglifyjs-webpack-plugin
  • babel-core is the core Babel package for transpiling ES6 and JSX into browser-friendly ES5 JavaScript.
  • babel-loader is a Webpack loader which loads files from our path into Babel.
  • babel-preset-es2015 is a preset for Babel to transpile ES6 code.
  • babel-preset-react is a preset for Babel to transpile JSX code.
  • webpack is Webpack itself.
  • uglifyjs-webpack-plugin is a Webpack plugin to uglify and compress our code for production.

Install React

Next, install the React packages, this time using --save to indicate these are runtime dependencies:

$ npm install --save react react-dom

Set up your Babel configuration file

Babel has its own configuration file inside .babelrc. Create this file in the root of your Middleman project with the following contents:

{
  "presets": [
    "es2015", "react"
  ]
}

This file tells Babel to use the es2015 and react presets we installed in the first step.

Configure Webpack

Next we'll configure Webpack with a basic configuration file that supports both development and production environments. Place the following inside the file webpack.config.js in the root of your project:

// webpack.config.js
var webpack = require('webpack');

const isProduction = process.env.NODE_ENV === 'production';

const productionPlugins = [
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': '"production"'
  }),
  isProduction ? new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false,
    },
  }) : null,
];

module.exports = {
  entry: './assets/javascripts/index.js',
  devtool: isProduction ? false : 'source-map',
  output: {
    library: 'MyApp',
    path: __dirname + '/tmp/dist',
    filename: 'bundle.js',
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: isProduction ? productionPlugins : []
};

Notice that we use the isProduction flag to toggle the use of the UglifyJsPlugin as well as whether we use a devtool to provide us with source maps for debugging.

Configure Middleman to build Webpack

Now that we've configured Webpack, let's tell Middleman to execute it whenever it rebuilds. To do this, we'll activate the external pipeline plugin in our config.rb and point it at the Webpack executable:

# config.rb

activate :external_pipeline,
  name: :webpack,
  command: build? ?
  "NODE_ENV=production ./node_modules/webpack/bin/webpack.js --bail -p" :
  "./node_modules/webpack/bin/webpack.js --watch -d --progress --color",
  source: "tmp/dist",
  latency: 1

When we run middleman build, we run Webpack with NODE_ENV=production. This triggers our production build options in our Webpack configuration.

When using middleman server though, we tell Webpack to --watch for changes and automatically rebuild.

Middleman will look for the result of our built assets inside tmp/dist. Let's go ahead and create that directory now:

$ mkdir -p tmp/dist

Build an example React component

Now that we've got our tooling configured, let's create a simple React component to test that everything works. First, create a directory to store your Webpack JavaScript assets. I place these files in assets/javascripts since Sprockets looks inside source/javascripts, and I don't want Sprockets to attempt to build my React code.

$ mkdir -p assets/javascripts

Next, let's sketch out a React component. Note that from index.js I'm exporting a function renderHello which renders the HelloWorld component to a DOM element specified by ID. This allows us to call upon fragments of React code from within existing pages.

// assets/javascripts/index.js

import React from 'react';
import ReactDOM from 'react-dom';

const HelloReact = props => (
  <div>Hello, React!</div>
);

function renderHello(id) {
  const el = document.getElementById(selector);
  ReactDOM.render(<HelloReact />, el);
}

export default {
  renderHello
}

Mount the component

Finally, mount the component onto an existing DOM element on a page. First include the built bundle.js file. Then, call renderHello:

  <body>
    <div id="hello">
    </div>

    <%= javascript_include_tag  "bundle" %>
    <script type="text/javascript">
      MyApp.default.renderHello('hello');
    </script>
  </body>

Other resources

How to name and organize your React components

When I first began building complex React applications, I struggled to determine the best way to name and organize my components within my codebase. Having come from the Rails school of convention over configuration, I was perplexed to find that most modern JavaScript apps rely heavily on custom configuration and don't adhere to any sort of community-driven conventional norms. That's changing slowly with the advent of toolkits like create-react-app, but even its conventions go right out the window as soon as you npm eject.

After a couple years of learning and mistakes, there are a few guidelines I use when organizing my React components so my code is more readable, understandable, and succinct.

Compose your components into smaller subcomponents

While it's tempting to just keep adding bits and pieces to your component's render method, this can grow to the point where it becomes difficult for new eyes to discern your intentions. Imagine a render method like this:

render() {
  return (
    <div className="todo-list">
      <div className="todo-list__items">
        {this.props.items.map(item => (
          <div className="todo-item">
            {item.title}
          </div>
        ))}
      </div>
    </div>
  );
}

Instead of rendering todo items within the root component's render method, create a new component:

const TodoItem = props => (
  <div className="todo-item">
    {props.item.title}
  </div>
);

Then, call it in our root component's render method:

render() {
  return (
    <div className="todo-list">
      <div className="todo-list__items">
        {this.props.items.map(item => (
          <TodoItem item={item} />
        ))}
      </div>
    </div>
  );
}

It's a subtle change, but making these sorts of changes proactively can keep your components readable. And becuase React only re-renders those components that change, extracting smaller components can improve your application's performance.

Extract iterators into class methods or new components

Extracting a subcomponent is a good first step. But we can go one step further by extracting the map iterator call into its own method in the root component. This improves the readability of the render method:

render() {
  return (
    <div className="todo-list">
      <div className="todo-list__items">
        {this.renderItems()}
      </div>
    </div>
  );
}

renderItems() {
  return this.props.items.map(item => (
    <TodoItem item={item} />
  ));
}

Now when we scan the render method, we see a more succinct summary of its contents!

Only use ES6-style class components where state is needed

Notice in my previous example that I've opted to use const to define the TodoItem component. This is because, in its current incarnation, the TodoItem component is stateless.

In React, stateless components are merely functions that return a React-wrapped DOM element. Unlike using the ES6 class keyword or the now-antiquated React.createClass method, stateless components cannot hold their own component state in a this.state object.

The reason this syntax is favorable is because it encourages authoring code in a functional style. Let's expand our TodoItem component to include a "Delete" button:

const TodoItem = props => (
  <div className="todo-item">
    <div className="todo-item__title">
      {props.item.title}
    </div>

    <button onClick={props.onDelete}>
      Delete
    </button>
  </div>
);

Here we've enclosed the title in a new container div, and then added a <button> tag with an onClick handler set to a hypothetical onDelete thunk handler that we would pass through from the parent component.

Just like in our iterator extraction example above, there's opportunity to make this component more readable. However, because this component is stateless we can use a more functional style:

const Title = props => (
  <div className="todo-item__title">
    {props.value}
  </div>
);

const DeleteButton = props => (
  <button onClick={props.onDelete}>
    Delete
  </button>
);

const TodoItem = props => (
  <div className="todo-item">
    <Title value={props.item.title} /> <DeleteButton
    <DeleteButton onClick={props.onDelete} />
  </div>
);

Our goal in doing these extractions is to reduce fatigue when scanning your components' code. New developers who visit this code for the first time will be greeted with components whose markup is only a few lines, making it much easier to parse and understand than if they were tens or hundreds of lines.

Keeping this habit early on will mean your codebase can grow and remain understandable to newcomers.

Organize composed components into subdirectories

So far, we've built the following components:

  • TodoList
  • TodoItem
  • Title
  • DeleteButton

As our codebase grows, the way we physically organize our code on disk is going to become more critical. I've experimented with a couple methodologies.

The first is to create a directory called components, dump all your components in there, and call it a day. This is fine for projects with fewer than 20 or so components, but it becomes cumbersome as the number of components grows.

Instead, I've settled on creating subdirectories for certain root-level components. In our example, we could envision the following directory structure:

  components/
    TodoList/
      index.js
      Item/
        index.js
        Title.js
        DeleteButton.js

Organizing our components in this way has the following benefits:

  • Components only ever reference other components within their own subdirectory.
  • We can name components based on their context. For instance, instead of naming our item component TodoItem, we can call it Item. This reduces unnecessary verbosity.
  • Our components become portable. By encapsulating their hierarchy within a single directory, we can reuse the component in other codebases easily.

Why React will make UI designers redundant... eventually

Material UI

A thought occurred to me today as I was knee-deep in a Material UI codebase: For a lot of MVP's, hiring a visual designer is largely irrelevant now.

Bootstrap popularized the idea of prefabbed component sets, but using it relies heavily on a soup of CSS classes ugly HTML. Because of this, most Bootstrap codebases I've worked on turn into a confusing mess of CSS overrides. Gross.

React components eliminate that entropy. Instead of shipping a framework consisting of complex markup that only works when used a certain way, frameworks like Material UI are able to deliver visually challenged developers a framework for developing pleasant-looking interfaces with ease.

Obviously, great products require forward thinking visual design direction and the aid of a dedicated designer---especially in the consumer space. And someone needs to direct the user's experience regardless of the interface aesthetic.

But for delivering rapid value, interface componentization means we're able to develop fully-working, beautiful prototypes without full mockups.

The way I see it, this trend will continue. Component sets will mature, React will be replaced by another more powerful component-based library, and new methods for rapid UI prototyping will emerge.

Testing ES6 React components with Enzyme's shallow rendering

I ran into a strange issue today when writing some assertions using the Enzyme testing library for React.

Whenever I create a new component, I like to use ES6 class notation and export the class anonymously like this:

// MyChildComponent.js
import React from 'react';

export default class extends React.Component {
  render() {
    return (<div>MyChildComponent</div>)
  }
}

Then, I'll render it in another component like this:

// MyParentComponent.js
import React from 'react';
import MyChildComponent from './MyChildComponent';

export default class extends React.Component {
  render() {
    return (
      <div>
        <MyChildComponent />
      </div>
    )
  }
}

When testing for the presence of MyChildComponent within MyParentComponent in Enzyme, I'll typically produce a test that looks like this:

import { shallow } from 'enzyme';
import { expect } from 'chai';

import MyParentComponent from './MyParentComponent';

describe("<MyParentComponent />", () => {

  const wrapper = shallow(<MyParentComponent />);

  it("renders a MyChildComponent", () => {
    expect(wrapper.find('MyChildComponent')).to.have.length(1);
  });

});

But this fails! It's as if MyChildComponent isn't being rendered at all.

If I dump wrapper.debug() (doc) to the console, I get this output in place of MyChildComponent:

<div>
<_class />
</div>

It's as if Enzyme doesn't know the component is called MyChildComponent!

Solutions

There are two ways to solve this.

Import the component itself and assert on it instead

Below, we import MyChildComponent and then, in the assertion, use the class constant instead of the string literal "MyChildComponent":

import { shallow } from 'enzyme';
import { expect } from 'chai';

import MyParentComponent from './MyParentComponent';
import MyChildComponent from './MyChildComponent';

describe("<MyParentComponent />", () => {

  const wrapper = shallow(<MyParentComponent />);

  it("renders a MyChildComponent", () => {
    expect(wrapper.find(MyChildComponent)).to.have.length(1);
  });

});

Export the named class from within the child component

As much as we should strive to write code that doesn't repeat itself, this was the solution I ultimately chose. It turns out React is able to determine the class name so long as you define it in the class statement. Modifying MyChildComponent.js to produce a named class and then exporting it allows Enzyme to find it in the string literal assertion:

// MyChildComponent.js
import React from 'react';

class MyChildComponent extends React.Component {
  render() {
    return (<div>MyChildComponent</div>)
  }
}

export default MyChildComponent;

If you can't seem to get an Enzyme assertion to find a component you know is there, make sure Enzyme knows what sort of component it is!

How to set up a test runner for modern JavaScript using Webpack, Mocha, and Chai

We've all been there: You're about to build another front-end feature. You know you want to start unit testing your JavaScript. You know that because React employs one-way data binding, it means writing tests is made easier than the Backbone MVC days of yore. But the setup... oh my, the setup. It's painful. There are so many tools, so much boilerplate. So you say to yourself, we'll do it next sprint.

But then the regressions start mounting. Your team is frustrated when QA sends back your work and tells you the new thing works, but that you broke 2 old things. And so now you're back to the grind, trying to ship a working build before the end of the week.

We've all been there, but let's put our procrastination to rest once and for all. The truth is, JavaScript testing is more awesome than ever. It might not be as distilled as say, Rails testing. But after reading this guide, you'll be able to go back to your team and proudly say this is the week you start testing your JavaScript.

If you've already read the guide, or just want to play around with some real, working code, I've prepared an example app here: Webpack+Mocha+Chai Example

Tools

Right now, the landscape of tools for testing JavaScript is large. In this guide, we're going to focus on what I've found to be the most productive combination:

  • Mocha to run our tests.
  • Chai to make assertions.
  • Webpack to glue everything together.

Install Packages

I'll assume you're already familiar with npm, have created a package.json file, and are using it in your project. If not, here's a tutorial to get you started. The npm command installs packages you want to use in your application and provides an interface for working with them. We're going to install the packages that will support our tests. Because these packages are for our development use only, we use the --save-dev option when running npm:

npm install --save-dev webpack mocha chai mocha-webpack

Create a Webpack Configuration

Webpack is a module bundler for the web. You might have used Browserify or CommonJS in the past to modularize your JavaScript. Webpack takes this paradigm a step further and lets you produce a dependency for just about any type of file. A full explanation of the tool is outside the scope of this tutorial, but Ryan Christiani has a great Introduction to Webpack tutorial to get you started.

For now, create a file webpack.config.js and fill it with the following:

var webpack = require('webpack');

module.exports = {
    module: {
        loaders: [
            {
                test: /.*\.js$/,
                exclude: /node_modules/,
                loaders: ['babel']
            }
        ]
    },
    entry: 'index.js',
    resolve: {
        root: [ __dirname, __dirname + '/lib' ],
        extensions: [ '', '.js' ]
    },
    output: {
        path: __dirname + '/output',
        filename: 'app.bundle.js'
    }
};

Configure Babel

Babel is a JavaScript compiler that allows us to use next generation JavaScript (ES6, ES7, etc) in browsers that only support ES5. As you'll see when we begin writing tests, having ES6 import statements and fat arrow function notation (() => { }) will make our tests more readable and require less typing.

You'll notice, in the loaders section above, we're using the babel loader to process our JavaScript. This will allow us to write our application and test code in ES6. However, Babel requires that we configure it with presets, which will tell Babel how it should process our input code.

For our example, we need just one preset: es2015. This tells Babel we want to use the ECMAScript 2015 standard so we can use things like the import and export statements, class declarations, and fat arrow (() => {}) function syntax.

To use the preset, we'll first install its package using npm:

npm install --save-dev babel-preset-es2015

Then, we'll tell Babel to use it by creating a .babelrc file:

{
    "presets": [
        "es2015"
    ]
}

Create the entry file and test Webpack configuration

Our Webpack configuration states that our entry file, the JavaScript module Webpack will run when our bundle is included in the page, is index.js. So let's create that file now. For now, let's just alert "Hello, World!". We're not going to run this code anyway, since we're really just using this entry file to be sure Webpack is configured properly.

// index.js

alert("Hello, World!");

Then we'll create an output directory. This is where we've configured Webpack to write our bundle file:

mkdir output

If we've configured everything properly, running Webpack should spit out our bundle file:

webpack

If the file output/app.bundle.js is present and you can locate our alert("Hello, World!") code in its contents, then you've configured Webpack successfully!

Set up the Mocha runner command

NPM has a scripts configuration option that allows creating macros for running common commands. We'll use this to create a command that will run our test suite on the command line.

In your package.json file, add the following key to the JSON hash:

{
  "scripts": {
    "test": "mocha-webpack --webpack-config webpack.config.test.js \"spec/**/*.spec.js\" || true"
  }
}

For an actual example of this command in a real package.json file, see the package.json file in the example code.

Dang though, that is one hefty command. Let's go through this piece by piece.

First, we're assigning this to the test command. That means that when we run npm run test, NPM will execute the mocha-webpack --webpack-config ... command for us.

The mocha-webpack executable is a module that precompiles your Webpack bundles before running Mocha, which actually runs your tests. Now, mocha-webpack is designed for server-side code, but so far I haven't had any problems using it for client-side JavaScript. Your mileage may vary.

When we call the mocha-webpack command, we pass it the --webpack-config option with the argument webpack.config.test.js. This tells mocha-webpack where to find the Webpack configuration file to use when precompiling our bundle. Notice that the file has a .test suffix and that we haven't created it yet. We'll do that in the next step.

After that, we pass mocha-webpack a glob of our test files. In this case, we're passing it spec/**/*.spec.js, which means we'll run all the test files contained within the spec folder and all folders within it.

And finally, we append || true to the end of the command. This tells NPM that in the event of an error (non-zero) exit code from the mocha-webpack command, we shouldn't assume something horrific went wrong and print a lengthy error message explaining that something probably did. Most of the time we run tests, a test or few will fail, resulting in a non-zero exit status. This addition cleans up our output a bit so we don't have to read a nagging error message each time. I'm sure the NPM team meant well when they added this message, but I think it's a bit silly we have to resort to this to remove it. If you know a better way, leave a comment!

Create our test Webpack configuration

Because we're running our tests on the command line and not in the browser, we need to be sure to tell Webpack that our target environment is Node and not browser JavaScript. To do this, we'll create a specialized test Webpack configuration which targets Node in webpack.config.test.js:

var config = require('./webpack.config');
config.target = 'node';
module.exports = config;

I also want to point out how nice it is that Webpack configurations are just plain JavaScript objects. We're able to require our base configuration, set the target property, and then export the modified configuration. This pattern is especially useful when producing production configuration files, but that's a topic for another guide.

Write a basic test

It's the moment we've been waiting for! We've laid the foundation for testing in our project. Now let's write a basic (failing) test to see Mocha in action!

Create the spec directory in your project if you haven't already. Before we get testing React components, let's just try our hand at testing a plain old function. Let's call that function sum, and test that it does indeed sum two numbers. I know, it's real exciting. But it'll give us confidence our test setup is working.

Create a file spec/sum.spec.js with the following:

import sum from 'sum';
import { expect } from 'chai';

describe("sum", () => {
    context("when both arguments are valid numbers", () => {
        it("adds the numbers together", () => {
            expect(sum(1,2)).to.equal(3);
        });
    });
});

Let's go over that one line at a time.

First, we import a function called sum from a module called 'sum'. You probably guessed we're going to need to create that file. You guessed right.

Create the file lib/sum.js:

export default function() { }

Note that we're creating the file inside the lib folder. Way back in step 2, we told Webpack that we should resolve modules in both the root folder as well as the /lib folder. We use lib because it indicates to other developers that this file is part of our application library code, as opposed to a test, or configuration, or our build system, etc.

Assertion Styles

The second line in our test file imports a function expect from the Chai module. Chai has a couple different assertion styles which dictate how tests will be written. Without going too far into the details, it means your tests could either read like this:

Assert that x is 10.

Or like this:

Expect x to be 10.

Or like this:

x should be 10.

This is largely a matter of developer preference. In my time as a developer, I've seen the Ruby community shift its consensus from assert, toward should, and now toward expect. So let's settle on expect for now.

Run our test suite

Now that we've created our spec/sum.spec.js file, let's go ahead and run our npm run test command:

npm run test

> react-webpack-testing-example@1.0.0 test /Users/teejayvanslyke/src/react-webpack-testing-example
> mocha-webpack --webpack-config webpack.config.test.js "spec/**/*.spec.js" || true

sum
  when both arguments are valid numbers
    1) adds the numbers together


0 passing (7ms)
1 failing

1) sum when both arguments are valid numbers adds the numbers together:
  AssertionError: expected undefined to equal 3
    at Context.<anonymous> (.tmp/mocha-webpack/01b73f0d4e3c95d9c729f459c86e1fc4/01b73f0d4e3c95d9c729f459c86e1fc4-output.js:93:61)

Success! Well, sort of. Our test runs, but it looks like it's failing because we never implemented the sum function. Let's do that now.

Make the test pass

Let's make our sum function take two arguments, a and b. We'll return the result of adding both of them together, like so:

export default function(a, b) { return a + b; }

Now run our test again. It passes!

npm run test

> react-webpack-testing-example@1.0.0 test /Users/teejayvanslyke/src/react-webpack-testing-example
> mocha-webpack --webpack-config webpack.config.test.js "spec/**/*.spec.js" || true

sum
  when both arguments are valid numbers
    ✓ adds the numbers together


1 passing (6ms)

Watch for changes to streamline your workflow

Now that we've written a passing test, we'll want to iterate on our math.js library. But rather than running npm run test every time we want to check the pass/fail status of our tests, wouldn't it be nice if it ran automatically whenever we modified our code?

Mocha includes a --watch option which does exactly this. When we pass mocha-webpack the --watch option, Mocha will re-run our test suite whenever we modify a file inside our working directory.

To enable file watching, let's add another NPM script to our package.json:

{
  "scripts": {
    "test": "mocha-webpack --webpack-config webpack.config.test.js \"spec/**/*.spec.js\" || true",
    "watch": "mocha-webpack --webpack-config webpack.config.test.js --watch \"spec/**/*.spec.js\" || true"
  }
}

Notice how the watch script just runs the same command as the test script, but adds the --watch option. Now run the watch script:

npm run watch

Your test suite will run, but you'll notice the script doesn't exit. With the npm run watch command still running, add another test to spec/sum.spec.js:

import sum from 'sum';
import { expect } from 'chai';

describe("sum", () => {
    context("when both arguments are valid numbers", () => {
        it("adds the numbers together", () => {
            expect(sum(1,2)).to.equal(3);
        });
    });

    context("when one argument is undefined", () => {
        it("throws an error", () => {
            expect(sum(1,2)).to.throw("undefined is not a number");
        });
    });
});

Save the file. Mocha will have re-run your suite, and it should now report that your new test fails.

Reduce duplication in package.json

In the previous step, we copied and pasted the test script into the watch script. While this works fine, copy and paste should bother every developer just a little bit.

Luckily, mocha-webpack provides a way to specify the default options to the command so we needn't include them in each line of our package.json's scripts section.

Create a new file called mocha-webpack.opts in your project's root directory:

--webpack-config webpack.config.test.js
"spec/**/*.spec.js"

And now, your package.json file can be shortened like this:

{
  "scripts": {
    "test": "mocha-webpack || true",
    "watch": "mocha-webpack --watch || true"
  }
}

Helpful links

How to bind React component event handlers in ES6

When creating React components using ES6 class notation, you'll need to bind event handlers passed as props to this, or you'll find that the handlers will be bound instead to the DOM element.

There are a few ways to do this. You can reassign the bound handler in the constructor:

class MyComponent extends React.Component {
  render() {
    return (
      <div>
        <button onClick={this.onClick.bind(this)}>Click Me</button>
      </div>
    );
  }

  onClick(event) {
    alert("You clicked me!");
  }
}

This works fine, but now you have .bind(this) littering your otherwise elegant JSX.

To remedy that, you can use the fat arrow prototype method syntax:

class MyComponent extends React.Component {
  render() {
    return (
      <div>
        <button onClick={this.onClick.bind(this)}>Click Me</button>
      </div>
    );
  }

  onClick = (event) => {
    alert("You clicked me!");
  }
}

Except... now you have two separate syntaxes for declaring methods, which could make your code less readable and more confusing.

I think the most elegant way is to use the new double-colon (::) notation, which is a shortcut for calling .bind(this) on a given handler:

class MyComponent extends React.Component {
  render() {
    return (
      <div>
        <button onClick={::this.onClick}>Click Me</button>
      </div>
    );
  }

  onClick(event) {
    alert("You clicked me!");
  }
}

Now your caller is binding the method to this without an ugly .bind(this) call, and the method body isn't unnecessarily decorated with fat arrow notation.

Why doesn't React immediately mutate state when calling setState?

When setting the state on a React component within an event handler, you'll find that the state isn't updated if you dump the state to the console immediately after:

The React documentation warns:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

I learned today that setState accepts a callback in this scenario. So modifying handleChange to the following will result in the console receiving the updated state: