From imperative to declarative

Is there is a way to write this loop inside calculateWinner function with forEach?

This function is from React tutorial to calculate the winner in a tic-tac-toe game.

const 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;
};

=============
I was trying to rewrite it in a declartive way, and here is what I end up with:

const 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],
  ];

  lines.forEach((line) => {
    const [a, b, c] = line;
    const checkWinner =
    squares[a] && squares[a] === squares[b] && squares[a] === squares[c];
    
    // wrong approach because
    // this return will stop the callback function, not the calculateWinner func
    return checkWinner ? squares[a] : null; 
  });

};

And here is the problem, I can’t use the calculateWinner 's return inside another nested function.
Is there is a way to tell JS that this return should be for the parent function, not for the current function? A silly question and I think the answer is no but maybe there is a way.

Also, I would really appreciate it if anyone could suggest another approach to rewrite this function with declarative programming.

I wouldn’t use forEach here but instead look at array methods such as .find since we are trying to “find” a match. I think one of the downsides to forEach is that you can’t just break out of it with a return statement. I also did this tutorial a while back and now refactored the function using .find method. It seems a bit more declarative since we avoid the for loop and also shortened the code a bit. Here is what I ended up with:

  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],
      ];

    const found = lines.find(line => {
      const [a, b, c] = line
      return squares[a] && squares[a] === squares[b] && squares[b] === squares[c]
    })
    return found ? squares[found[0]] : null
  }
1 Like

Interesting! I’m not using .find a lot that’s why it didn’t come to my mind.
Thank you for sharing this!

1 Like