Search code examples
javascriptfunctionscopedry

How to avoid variable scope issues within a map function?


I have a working solution of this problem but I'm trying to make a cleaner and neat version of it as much as possible. I came up with another solution that uses a function within a map function. Unfortunately, this version has a few issues and I want to just know why the second solution is not working. I'm guessing it's a variable scope issue here. I'm looking forward to know your opinion about it.

I have a simple function that prints calendar days in an array!

So a question is why the first version of my code get the expected results while the second version prints unexpected results.

I tried to change let to var and I also made the counter and startedIndexing outside the function scope.

Solution 1 (works):

const currentFullMonth = {
   days_length: 31,
   first_day: "Thu",
   first_day_index: 4,
   last_day: "Sat",
   last_day_index: 6,
   month: "Aug",
   year: 2019
}

const testMonth = [
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0]
];

function printMonthCalender(month) {
    let counter = 0;
    let startedIdxing = false;
    return month.map(week => {
        return week.map((day, index) => {
            if (index === currentFullMonth.first_day_index && !startedIdxing) {
                counter++;
                startedIdxing = true;
                return counter;
            } else if (startedIdxing) {
                if (currentFullMonth.days_length === counter) {
                    counter = 0;
                }
                counter++;
                return counter;
            } else {
                return 0;
            }
        });
    });
} // end of Solution #1 <-- this works :)

Solution 2 (doesn't work):

// start of Solution #2 <-- does not work :(    
// im using two functions to make it look more cleaner
//
function printMonthCalender2(month) {
    let counter = 0;
    let startedIdxing = false;
    return month.map(week => {
        return week.map((day, index) =>
            indexingMonth(counter, startedIdxing, index)
        );
    });
}
function indexingMonth(counter, startedIdxing, index) {
    if (index === currentFullMonth.first_day_index && !startedIdxing) {
        counter++;
        startedIdxing = true;
        return counter;
    } else if (startedIdxing) {
        if (currentFullMonth.days_length === counter) {
            counter = 0;
        }
        counter++;
        return counter;
    } else {
        return 0;
    }
}// end of Solution #2

console.log(printMonthCalender(testMonth));
console.log(printMonthCalender2(testMonth));

expected result as follows (first version):

[0, 0, 0, 0, 1, 2, 3]
[4, 5, 6, 7, 8, 9, 10]
[11, 12, 13, 14, 15, 16, 17]
[18, 19, 20, 21, 22, 23, 24]
[25, 26, 27, 28, 29, 30, 31]
[1, 2, 3, 4, 5, 6, 7]

unexpected result as follows (second version):

[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 0, 1, 0, 0]

Solution

  • The problem is that when you reassign startedIdxing inside of indexingMonth, it's a local variable, so it doesn't get changed inside of the calling function (printMonthCalender2).

    An issue is that .map should not have mutation or reassignment as a side-effect. While you could tweak things so that indexingMonth returned something that you checked and then reassigned startedIdxing to, I'd prefer a different approach: create a flat array, eg

    [0, 0, 0, 0, 1, 2, ..., 30, 31, 1, 2, 3]
    

    and then chunk it into pieces of 7 afterwards:

    const currentFullMonth = {
       days_length: 31,
       first_day: "Thu",
       first_day_index: 4,
       last_day: "Sat",
       last_day_index: 6,
       month: "Aug",
       year: 2019
    }
    
    const makeZeroArr = length => new Array(length).fill(0);
    const printMonthCalendar = (testMonth) => {
      // Create array: [1, 2, 3, ..., 30, 31]
      const oneMonth = Array.from(
        { length: currentFullMonth.days_length },
        (_, i) => i + 1
      );
      // Create a flat array with leading zeros and trailing last week:
      // [0, 0, 0, 0, 1, 2, 3, ..., 30, 31, 1, 2, 3, 4, 5, 6, 7]
      const flatResultArr = [
        ...makeZeroArr(currentFullMonth.first_day_index),
        ...oneMonth,
        ...oneMonth // this includes extra numbers that will be trimmed
      ].slice(0, 7 * 6); // 7 days/week * 6 weeks
      // Chunk the flat array into slices of 7:
      const resultArr = [];
      for (let i = 0; i < 7; i++) {
        resultArr.push(flatResultArr.slice(i * 7, (i + 1) * 7));
      }
      return resultArr;
    };
    
    console.log(printMonthCalendar());