Refactoring class component to React functional component

Looking at the original, it seems the Rules class doesn’t do much more than save the params its given (via assign) and set up a few helper functions, none of which actually refer to those params. This is good because they can be easily ripped out and made into simple, standalone functions - something you’ve already done, so off to a good start.

After that there are some other classes that all extend Rules, but all of these only ever define a single evalRoll method. So really, these can effectively just be that method as a function. You’re putting them in objects in your refactored code which isn’t necessary since they all simply wrap one function. Instead just make them the function. So

class TotalOneNumber extends Rule {
  evalRoll = dice => {
    return this.val * this.count(dice, this.val);
  };
}

would loosely become

function totalOneNumber(dice) {
  return this.val * this.count(dice, this.val);
}

The problem now is getting rid of this. this would represent the current instance and the members within that instance but we’re not dealing with classes an instances anymore, just functions with simple inputs and outputs.

There’s two uses of this, one for val and one for count. count is easy, that’s a function we ripped out earlier so that can just drop the this and simply work. val is something that would be coming from the params passed into the constructor during instantiation of the class. Its another kind of input, one that would be coming from the instance rather than the arguments, but we can simply move it to be an argument input instead because we no longer have an instance. That gives us

function totalOneNumber(val, dice) {
  return val * count(dice, val);
}

You can then repeat this process for the other classes as well.

After the classes, we set the individual numbers being instantiated from the TotalOneNumber class. Each of these instances contain the functionality of the TotalOneNumber class but each having a different val property (there’s a description too, but we can ignore that for now since I don’t know how it’s being used).

Since the refactor doesn’t have instances, these would instead have to be represented as functions… or do they? Wouldn’t each of these instead just calling the new totalOneNumber(val, dice) function with a different val argument each time? Well, yes, but we can also make new function versions of totalOneNumber(val, dice) with those values baked in using partial application. JavaScript can do this with the native bind() method. That would look something like this:

const ones = totalOneNumber.bind(null, 1);
const twos = totalOneNumber.bind(null, 2);
const threes = totalOneNumber.bind(null, 3);
const fours = totalOneNumber.bind(null, 4);
const fives = totalOneNumber.bind(null, 5);
const sixes = totalOneNumber.bind(null, 6);

Now, each of these are new functions which calls totalOneNumber(val, dice) with the val pre-defined. ones, for example, would have the signature of ones(dice) and would use 1 as the value of val in its function body which is that of totalOneNumber, or more specifically something like:

function ones(dice) {
  return 1 * count(dice, 1);
}

Lets test it out (full script with all necessary dependencies so you can copy and paste and run somewhere):

function count(dice, val) {
  return dice.filter(d => d === val).length;
}

function totalOneNumber(val, dice) {
    return val * count(dice, val);
}

const ones = totalOneNumber.bind(null, 1);
const twos = totalOneNumber.bind(null, 2);
const threes = totalOneNumber.bind(null, 3);

const dice = [1,1,2,2,3];
console.log('ones:', ones(dice)); // 2 (1x2)
console.log('twos:', twos(dice)); // 4 (2x2)
console.log('threes:', threes(dice)); // 3 (3x1)

The other classes aren’t very different and would need a similar treatment. You think you could do the rest yourself? Just be sure to not confuse the count parameter and the count function. When you’re done there should be no references to this or new. What you’ll be exporting will be a list of functions, not a list of object instances, and these functions would be used in place of instance.evalRoll() (e.g. what was ones.evalRoll(dice) would instead become ones(dice)).

3 Likes