Click here to Skip to main content
15,887,214 members
Articles / All Topics

Roll the Dice: Random Numbers in Redux

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
21 Feb 2017CPOL2 min read 5.7K   1
Roll the dice: Random numbers in Redux

How would I model calling something like Math.random() in Redux's world?

One of the tenets of Redux is that reducer functions must be pure. What about when you want to do something impure, like generate a random number, or get the current date?

Recap: What's a Pure Function?

A pure function is one that follows these rules:

  • No side effects: It can't change anything outside the function's scope (this also means it can't modify its arguments)
  • Same output for same input: Calling it with a given set of inputs must produce the same return value, every time (this means no saved state between calls)

Here's an impure function:

JavaScript
function addItem(items, item) {
  items.push(item);
}

// Used like:
let items = [1, 2];
addItem(items, 3);

It's impure because it modifies one of its arguments.

Here's another impure function:

JavaScript
function makePerson(firstName, lastName) {
  // Make an age between 1 and 99
  const age = Math.floor(Math.random() * 99) + 1;

  return {
    name: firstName + " " + lastName,
    age: age
  };
}

This one is impure because it'll (probably) return a different result when given the same inputs. Call it 3 times like makePerson('Joe', 'Smith') and it will return people with 3 different ages.

Impure Values in Redux

Dice

Dice by Ella's Dad. CC BY 2.0.

Now let's say you need to do something impure, like simulate the roll of two dice, and put the result in the Redux store.

We already know that reducers must be pure - so we can't call Math.random() in the reducer. Anything impure must come in through an argument. Here is our reducer:

JavaScript
const initialState = {
  die1: null,
  die2: null
};

function diceReducer(state = initialState, action) {
  switch(action.type) {
    case 'RESET_DICE':
      return initialState;

    case 'ROLL_DICE':
      //
      // then a miracle occurs
      //
      return state;

    default:
      return state;
  }
}

The only argument we can affect is action, which we can do by dispatching an action.

So that's what we'll do: put the random number into an action.

Option 1: Inside Action Creator

Here's a straightforward way to do this: generate the random number in an action creator.

JavaScript
function rollDice() {
  return {
    type: 'ROLL_DICE',
    die1: randomRoll(),
    die2: randomRoll()
  }
}

function randomRoll(sides = 6) {
  return Math.floor(Math.random() * sides) + 1;
}

Then dispatch it as usual, with dispatch(rollDice()).

Pros: It's simple.

Cons: It's impure, so it's harder to test. What're you gonna do, expect(rollDice().die1).toBeCloseTo(3)? That test will fail pretty often.

Option 2: Pass to Action Creator

Here's a slightly more complicated way: pass in the random numbers as arguments to the action creator.

JavaScript
function rollDice(die1, die2) {
  return {
    type: 'ROLL_DICE',
    die1,
    die2
  };
}

// Then elsewhere in component code...
dispatch(rollDice(randomRoll(), randomRoll()));

function randomRoll(sides = 6) {
  return Math.floor(Math.random() * sides) + 1;
}

Pros: The action creator is pure, and easy to test. expect(rollDice(1, 2).die1).toEqual(1).

Cons: Anything that calls this action creator must know how to generate the random numbers. The logic isn't encapsulated in the action creator (but it's still pretty well encapsulated in the randomRoll function).

Back to the Reducer

Whichever option you choose, the reducer is the same. It returns a new state based on the die values in the action.

JavaScript
const initialState = {
  die1: null,
  die2: null
};

function diceReducer(state = initialState, action) {
  switch(action.type) {
    case 'RESET_DICE':
      return initialState;

    case 'ROLL_DICE':
      return {
        die1: action.die1,
        die2: action.die2,
      };

    default:
      return state;
  }
}

Wrap Up

There's not too much else to say about impure values in reducers. To recap:

  • Reducers must be pure! Don't call Math.random() or new Date().getTime() or Date.now() or any other such thing inside a reducer.

  • Perform impure operations in action creators (easy to write, hard to test) or pass the values into the action creators (easy to test, harder to write).

Roll the Dice: Random Numbers in Redux was originally published by Dave Ceddia at Dave Ceddia on February 21, 2017.

This article was originally posted at https://daveceddia.com/feed.xml

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Dave is a Software Engineer in the Boston area and writes about AngularJS and other JavaScript things over at daveceddia.com

Comments and Discussions

 
QuestionProblems with some "chars" Pin
Nelek22-Feb-17 0:05
protectorNelek22-Feb-17 0:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.