Immutable Data

When creating a complex app, managing state can be complicated. The same state can be shared by many components.

It's possible that an interaction with one component can cause a data mutation at the same time that another component is trying to access the data. This can lead to bugs that are difficult to diagnose. Keeping your data immutable can prevent this.

Accessing State

When you need to update the state of a component, you first need to get it. You do that with a simple assignment:

// Method of a component:
updateName(newName) {
  const state = this.state
  state.name = newName
  this.setState(state)
}

Primitive Types vs Complex Types

JavaScript types are of two categories: primitive and complex. Primities are boolean, number and string. These are all immutable as well. This means that when you assign one of them to a variable and then assign that to another variable, a new copy is made:

const a1 = 1
const b1 = one
a1 = 2
console.log(a1) // returns 2
console.log(b1) // returns 1

String, numbers and booleans are passed by value.

Complex Types

The other category of types are objects, arrays and function. These are reference types. That means that when you assign them to one variable, the variable does not hold the value. It holds only a reference to the data. This means when you assign that to another variable, both will point to the same data. Chaning one appears to change the value of the other. Actually, with reference types there is only one, the variables are all point to the same value. Modifying that value from any variable changes the value for all references.

const person1 = {
  name: 'Joe',
  age: 26
}
const person2 = person1
person2.name = 'Sam'
person2.age = 32
console.log(person2)
// returns:
{
  name: 'Sam',
  age: 32
}
console.log(person1)
// returns:
{
  name: 'Sam',
  age: 32
}

This is the behavior for objects, arrays and functions. Most of the time your component data will be either an object or an array of either primitie types or objects.

Immutable Objects

The easiest way to get a new copy of the state's data when it is an object is to use Object.assign:

// Component method:
updateName(name) {
  // Make a copy of the state data:
  const state = Object.assign({}, this.state)
  state.name = 'Hellen',
  state.job = 'Project Manager'
  this.setState(state)
}

Immutable Arrays

We can easily clone an array using the native slice method. When no arguments are provided, it returns a copy of the array:

addItem(newItem) {
  // Make a copy of the state array:
  const state = this.state.slice()
  state.push(newItem)
  this.setState(state)
}
//or:
updateItem(data, position) {
  // Make a copy of the state array:
  const state = this.state.slice()
  state.splice(position,0, data)
  this.setState(state)
}

When an array holds objects, the above approach will not work. Even though we can clone the array, the objects themselves get copied by reference. To get arround this you can use a special array cloning function to copy their objects:

function immutableCollection(arr) {
  return arr.map(object => Object.assign({}, object))
}

class Users extends Component {
  constructor(props) {
    super(props)
    this.state = [
      {
        name: 'Joe'
      },
      {
        name: 'Sandra'
      },
      {
        name: 'Bob'
      }
    ]
  }
  updateUserName(name, position) {
    const user = immutableCollection(this.state)
    user[position].name = name
    this.setState(user)
  }
}

Creating a utility function to clone the array's objects lets us preserve the immutability the original state.

Why Immutability?

Seems like a lot of work to maintain immutability. It is. But it has its value. As we mentioned, since state is what drives your app, making changes to it directly in a piecemeal manner can cause some partial updates to the UI that result in unexpected behaviors or appearnances. It's always safest to create a clean copy of the state, make all the necessary changes, and then update the state with setState. Immutability also makes it easy to implement features such as undo and time travel.

Let's Time Travel

Demo time! Let's build a game of tic-tac-toe with time travel. This will show why immutable state is so eassential for complex apps.

The Board

Tic-Tac-Toe starts with a board--nine squares. We could make a simple function component like this:

function Board() {
  return (
    <div>
      <button></button>
      <button></button>
      <button></button>
    </div>
    <div>
      <button></button>
      <button></button>
      <button></button>
    </div>
    <div>
      <button></button>
      <button></button>
      <button></button>
    </div>
  )
}

That's the general idea, but we want to modularize thing, breaking out into three parts: Game class, board component and square component. Here's the start:

See the Pen Composi Tic Tac Toe - 1 by Robert Biggs (@rbiggs) on CodePen.

Parent is in Control

We want the game class to be the part controlling everything--state and events. We'll therefore need to pass data down to the child components. In the next example you can see how we pass data from the board down to the squares through props:

See the Pen Composi Tic Tac Toe - 2 by Robert Biggs (@rbiggs) on CodePen.

We will be using this technique of passing data down in more ways as we develop this game.

Interactive Squares

We want the player to be able to tap a square to set its value as an X or O. For this to work we will put an onclick event on each square, and set up the event handler on the Game class. We'll call the Game method handleClick. We pass it from Game to the Board component as the prop onClick. And in the Board component, we will pass it to the Square component as the prop onClick. Then, in the Square component we can assign that onClick prop to an onclick event.

Immutable Data

And here we begin with the immutable data approach. Rather than just getting the array from the component's state, we get a copy of it using slice() on the array. Check out the handleClick event to see how we do that. We make a copy of the state array, make changes to the copy and then use setState to update the component's state:

See the Pen Composi Tic Tac Toe - 3 by Robert Biggs (@rbiggs) on CodePen.

Taking Turns

Next we want to show the moves as they take place, X followed by O. We want to show those choices on the squares and also let the players know whose turn is next. For that we'll add a new property to the state: xIsNext. By setting its initial value to true we indicate that the first move is X.

See the Pen Composi Tic-Tac-Toe 4 by Robert Biggs (@rbiggs) on CodePen.

So, we can show whose turn is next, but the square aren't updating. We'll need to make some more changes to the state. We're going to rename values to squares. This might seem petty now, but later its usage will get more complicated, so we're renaming it to indicate that it represents the squares. Since we renamed it, we need to change the reference in Button and Board as well, no big deal. Now, in the handleClick method we're going to change values[i] = 'X' to squares[i] = this.state.xIsNext ? "X" : "O". With that are squares are happening:

See the Pen Composi Tic-Tac-Toe 5 by Robert Biggs (@rbiggs) on CodePen.

The Winner

Yeah, a small detail. As it is, the plays can play and even when, but you can still keep choosing squares. We need to be able to declare a winner when someone winners.

To determine a winner, we need to know what are the moves that determine a winner. And then we need to compare our state of moves to see if there's a match. Here's the function to determine the winner:

// Calculate the winner:
function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ]
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i]
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a]
    }
  }
  return null
}

This function needs the squares. We're going to put this calculation into Board. We'll also need to update the status to show a winner instead of whose next:

const winner = calculateWinner(props.state.squares)
let status
if (winner) {
  status = 'Winner: ' + winner
} else {
  status = 'Next player: ' + (props.state.xIsNext ? 'X' : 'O')
}

And here it is working:

See the Pen Composi Tic-Tac-Toe 6 by Robert Biggs (@rbiggs) on CodePen.

This is an improvement. The players can take turns and they know when someone wins. However, They can still take turns. We need to stop that as soon as there's a winner.

Lifting State Up

At the moment we have Board deterimining who won. Really, Game should own state and state decisions. We are going to move that to Game's render method. To get the status to the board, we'll have to pass it down as a prop. And we'll need to check if there was a winner in the handleClick method. If there was a winner, we want to stop any further action.

See the Pen Composi Tic-Tac-Toe 7 by Robert Biggs (@rbiggs) on CodePen.

Storing History

So far we are just storing what moves the user made in the squares array. This works for determining a winner, but there is no way to tell who made what move. For that we'll need to take the immutability to the next level. We'll need to store an array of each move. This means we'll have to make some changes to our state structure. We want this:

history: [
    {
      squares: [null,null,null,null,null,null,null,null,null]
    },
    {
      squares: [X,null,null,null,null,null,null,null,null]
    },
    {
      squares: [X,null,O,null,null,null,null,null,null]
    }
]

This will enable us to examine all moves, and even time travel through the moves. For this we need to update the Game constructor. Notice how we simplified the creation of the initial array using Array(9).fill(null):

constructor(props) {
  super(props)
  this.state = {
    history: [{
      squares: Array(9).fill(null)
    }],
    stepNumber: 0,
    xIsNext: true
  }
}

Because of this change, we'll need to update all references to square through our code. We'll especially have to refactor the handleClick method:

handleClick(i) {
    const history = this.state.history
    const current = history[history.length - 1]
    const squares = current.squares.slice()
    if (calculateWinner(squares) || squares[i]) {
      return
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O'
    this.setState({
      history: history.concat([{
        squares: squares
      }]),
      xIsNext: !this.state.xIsNext,
    })
  }
}

And here's the new version. The user interaction hasn't changed. But we now have a history of what the moves were. is you add window.game = game, you can enter game.state.history in the Codepen console to see the history of moves. Of course you'll have to make some moves first ;-)

See the Pen Composi Tic-Tac-Toe 7 by Robert Biggs (@rbiggs) on CodePen.

One note: since the game is now determing moves and the winner, we moved the display of that information from the board to the game info panel to the side of the board. We'll be using this side panel to show user moves and enable time travel further ahead.

Stepping into the Past

In order to enable time travel, we'll need a way for the play to set back to previous moves. We'll do that by create a list of buttons for each move. Currently we have a side panel that outputs the game status:

<div class="game-info">
  <div>{status}</div>
  <ol>{/* TODO */}</ol>
</div>

That ordered list is where we want to put our steps through history.

The first thing we'll need is a function to step through the history. For now we just want a shell because we are first going to output the buttons:

jumpTo(step) {
  return
}

Now we can create those step buttons. We'll add a new function to the Game render function. It will create the buttons based on the current history. Each button will have a reference to the index of the history array it needs to access, and each button will fire the jumpTo method:

const moves = history.map((step, move) => {
  const desc = move ?
    'Go to move #' + move :
    'Go to game start'
  return (
    <li key={move}>
      <button class='button-moves' onclick={() => this.jumpTo(move)}>{desc}</button>
    </li>
  )
})

To output these we just need to put the variable moves into the unordered list:

<div class="game-info">
  <div>{status}</div>
  <ol>{moves}</ol>
</div>

With these in places, as a user makes moves, buttons for those moves will appear in side list. Try it out:

See the Pen Composi Tic-Tac-Toe 9 by Robert Biggs (@rbiggs) on CodePen.

Time Travel

Now that we have the buttons for moves, we can add the ability to travel through the game history by tapping those buttons. For that we need to update the jumpTo method:

jumpTo(step) {
  this.setState({
    stepNumber: step,
    xIsNext: (step % 2) === 0
  })
}

We also need to update the handleClick method to set state with a new property, stepNumber and refactor how we get history from state:

handleClick(i) {
  const history = this.state.history.slice(0, this.state.stepNumber + 1)
  const current = history[history.length - 1]
  const squares = current.squares.slice()
  if (calculateWinner(squares) || squares[i]) {
    return
  }
  squares[i] = this.state.xIsNext ? "X" : "O"
  this.setState({
    history: history.concat([{
      squares: squares
    }]),
    stepNumber: history.length,
    xIsNext: !this.state.xIsNext
  })
}

We'll also need to udpate the render function to use the stepNumber for the current slice of history:

const current = history[this.state.stepNumber]

With this we now have time travel:

See the Pen Composi Tic-Tac-Toe 10 by Robert Biggs (@rbiggs) on CodePen.

Summary

This has been a long journey. Hopefully you were able to follow along. Examine all the Codepen examples carefully. We started off with a simple board and slowly added more functionality. As we advanced, we had to make sure that the Game component owned state and interaction and that data was immutable. The Board and the Square became dumb components. They do not know about state and they do not own the interactions that affect state. That's all owned by the Game parent, which passes these down to them.

Now about that immutability, if we had not implemented that, a change to one index of the history array would update all the others. In effect we would not have any history. If we were not aware about how arrays are copied by reference, we would have been banging our heads on the wall wondering what was going on.

Using the techniques we used in this game, you can also implement time travel for other situations where it would be appropriate. This is especially useful if you need to enable the user to undo an action or two. For this game, there are only 9 possible steps for travel. If you intend on using time travel, limit it to the bare minimum. Otherwise, excessive use will eat up valuable memory, especially for mobile users.

Show the Winning Moves

For a final treat, we've updated the game to show the winning moves on the board. For this we had to change what calculateWinner was returning. We want to return the winner and the lines. Then in the Board, we'll add a won class to the appropriate squares.

return {
  who: squares[a],
  line: lines[i]
}

Since we're no longer returning the winner as a letter but as an object property who, we need to update how we render that in status:

status = "Winner: " + winner.who

And here it is complete:

See the Pen Composi Tic-Tac-Toe 11 by Robert Biggs (@rbiggs) on CodePen.

ImmutableJS

So, there is a framework called ImmutableJS that makes sure your data is always immutable. It was created and is maintained by Facebook. There is only one problem about using it with Composi. By default they use CommonJS format for the module. Unfortunately in its current state it can't be imported as an ES6 modules for use in a Composi project. There is a work around. You can down load the source code from Github and build it. Inside the resulting dist folder you will find: immutable.es.js. You can manually copy this to your Composi project and then import it like any other ES6 module. Yeah, it's annoying, but it does work. Currently they do not expose the ES6 version in the default install from NPM.