You should give React a try - in case you didn’t already. Despite my initial skepticism, it is developer friendly and straightforward. Coming from Angular, it’s like programming in a lower abstraction level; but the libraries for common problems, along with less unpleasant surprises make up for this. Componentization and the virtual DOM are React’s central concepts. Componentization is the general direction in most contemporary technologies too, like Web Components and Angular Directives. The latter makes the render and the event cycles two distinct flow, making reasoning about the code easier.
This post covers most of the building blocks of a basic Tic Tac Toe game. It utilizes the most recent React version along with some useful libraries. In the end, you should have a good general understanding of the technology.
As seeing the big picture is one of the most important task, here is a live, clickable demo of what we are building. Have fun!
Declaring a dependency on Babel along with it’s two presets we’ll be using is the first thing to do.
We then need to modify the Gruntfile so that it includes Babel.
For the full Gruntfile, please refer to GitHub. This defines a grunt dev task that watches the src directory and auto-compiles to dist.
JS Tips newsletter
Subscribe now and get it
Defining the components and their possible states are the first and most important part when developing a React app. React components are like Directives in the Angular world. They are HTML-like elements that encapsulate state inside, can have some attributes (called props in React), and are the main building blocks of more complex components.
When thinking in React, you should visualize that props will go downwards and events upwards. The state should be declared in the topmost component that would need it, as a rule of thumb. For example, all the components need to know whether a game is started; however the actual board is only the needed by the Game.
We’ll have three components for this application:
- Header, responsible for the textual information and the Start game button.
- Game, the main part, containing the board
- TicTacToe, the main entry point and container of the other two
These are the components and the states:
- gameStarted: True if the game is started
- crossTurn: True if X is the current player
- winner: “X”, “O”, or undefined to indicate the winner
Header, without any state
- table: The current table in a 3x3 matrix
- highlighted: The hovered coordinate in array format
(The complete source can be found here)
React.createClass, along with the configuration, defines a component. A render() method is required at a minimum. (Side note: ES6 classes can be used, but they come with some drawbacks. It is safer to use createClass for now)
The render function defines how the component will look when added to the page. It utilizes JSX; you can write some fancy logic inside; we’ll cover it later. You are free to define other arbitrary functions; they can be called and passed as props.
getInitialState() is a special function that initializes the this.state variable. Defaults should be put there. Use this.setState() to modify the state, which does a shallow merge of the current state and the object passed.
Other methods we’ll need:
- startGame(): Sets the state to reflect that the game is started
- placed(): Called when the current player placed a mark
- endGame(): Called once the game is ended
This is a simple component; it’s main purpose is to bootstrap and wire in the other two.
This is another simple component (source here); it just shows some static text and, conditionally, a button. Conditional rendering can be done in several ways in JSX; a simple guard might very well be the simplest:
Classes can be added using classNames in JSX. Attaching event handlers is just passing a function to the handler; the onClick attribute in this case. When clicked, React will call the function with a SyntheticEvent. Passed-in functions are simple to call, as they are in the this.props variable.
PropTypes are React’s way for defining an interface and types for a component. You can specify what props you expect for the component, which are required, and what are their types. The Header is using these:
If the types passed in does not conform to the ones specified here, you’ll see a runtime error in the browser console when running the application. Without static types, an erroneous configuration might slip through unnoticed, but it is a great help anyway.
Defining PropTypes is optional, but highly recommended. It help catch errors in an earlier stage; also adds an easy overview how to use the component.
This is where all the magic happens (code here). It has a nontrivial render(), along with a few caveats.
The primary concept when writing a render() method is that it is for a particular state and props. Dynamics are nothing to worry about at this stage. On the other hand, events are to modify the state. When you handle the events, you don’t need to worry about how it would manifest as HTML. This separation greatly simplifies coding with React.
Mutating this.state directly without using this.setState() is another pitfall. This is plain wrong, as it does not guarantee a rerender. Make sure you don’t modify objects and arrays in the state, as it would also have strange and hard-to-debug effects. To this, along with an emptyBoard(), there is also a getModifiedBoard() that returns a new array. Freezing the objects/arrays right before adding them to the state is also a best practice.
Nesting two of these maps draws the board. Babel takes care of browser compatibility; maps and similar ES6 constructs can be used freely.
Components have lifecycle events when the props and/or the state is changed. We can use componentWillReceiveProps to listen for props modifications, and componentDidUpdate for both state and props changes. The former should be used when we intent to use setState, as using it in the latter could result in an infinite loop.
ComponentWillReceiveProps is called with the next props; these values can be compared with those in this.props. In this example, an empty board is generated when a game is started.
componentDidUpdate gets both the previous props and the previous state. The game ending is detected here. If one of the players is winning, then we bubble this to the main component. We also detect a draw, as if no players won, but the board is full.
Hovering can be done using the onMouseOver and onMouseLeave events. bind() can be used to set parameters to the event handling function.
The highlightMove will then get the rowIndex and the cellIndex parameters:
setState can be called multiple times, it does not affect performance. React collects all such calls and only set the state after the handler is done.
A conditional class is the final piece we need for the hovering effect. Using string concatenations and ternary operators works fine for manipulating the classNames attribute , but it quickly gets tedious. Use the classnames library instead.
Closing remarks, a few tips you’ll find useful when working with React:
- Use the React devtools for development. It is a browser plugin that provides all sorts of helpful functions.
- Don’t optimize the render cycles in the expense of code readability, unless it is slow. Measuring it does not count as slow. Feeling it is. Also make sure to check it without the devtools open; it makes a big impact on performance.
- Make sure you don’t accidentally modify the objects and arrays inside this.state. You can even freeze them to make sure they are immutable.
- Make sure to remove temporary values from the state once they are not needed. For example, the highlighted value needs to be reset when the player places a mark. Failing to do so may result in hard-to-spot bugs.
- Define the propTypes for all your components. It will help to make sure all important props are present and in the right format.
- Use a React-specific linter, like eslint-plugin-react. It catches most of the potential problems before they manifest themselves as bugs.