javascript – Why does my date variable reset in my map

I wasn’t quite happy with the accepted answer and its discussion, so here comes a rather lengthy answer to provide some additional context. I hope it might be helpful, if not then that’s tough luck, I guess.


Logically, your problem is that you initialize your variable inside the [...Array(rows).keys()].map callback (let date = 1). Hence, moving the initialization outside the loop will fix this problem.

Syntactically, you can’t just move it outside of the map call, because you’re using JSX, which just provides syntactic sugar for a React.createElement(component, props, ...children) call. While your map call returns an array of JSX elements, and hence a valid argument for childrena variable declaration using let in its place will lead to a syntax error.

Extracting your rendering logic to a separate function in order to move the initialization before the loop, as shown by other answers, addresses this problem. Importantly, however, it does not extract the same logic but enables you to slightly change your logic (by moving the initialization outside of the loop). Also, the new function is not absolutely necessary, other syntactical hacks let you achieve the same thing, eg:

<div key={row} className={styles.row}>
  {[0].map((e) => {
    let date = 1;
    return [...Array(cells).keys()].map((cell) => {
      if (row === 0 && cell < firstDay) {
        return <div key={cell} className={styles.cell}></div>;
      } else if (date > daysInMonth(currentMonth, currentYear)) {
        return;
      } else {
        const cellText = String(date);
        date++;
        return (
          <div key={cell} className={styles.cell}>
            {cellText}
          </div>
        );
      }
    });
  })}
</div>;

I believe in your case, you could even just move the initialization to the top of your component.


The situation you ended up in is one of callback functions with side effects (the date declared outside the callback is changed from within the callback), which often negatively impacts the readability and maintainability of your code. JSX works well with a declarative approach and Functional Programming (as your use of map shows).

Below an example of how you could achieve your goal by calculating number of the day without side effects.

To calculate which number to display in each cell, it relies on the cell index and factors in the offset of the specific month. The indexes used for the calculations of are provided by map as the second parameter to the callback function. Step by step:

  1. It calculates the days processed before the current week, derived from the current weekIndex

  2. To that it adds the number of days processed in the current week, derived from the current dayIndex

  3. To that it adds +1 to arrive at the 1-based cell index. This is required because dayIndex (like all indexes in JavaScript) is 0-based.

  4. The 1-based cell index is only the correct day number if the first day of the month fell on a Sunday (offset = 0). Therefore, the calculation factors in the offset of this month.

The result is a concise function with an inexpensive calculation:

const calculateDay = (weekIndex, dayIndex, offset) =>
        7 * weekIndex + dayIndex + 1 - offset;

// Friday April 1 : 7 * 0 + 5 + 1 - 5 = 1
// Monday April 11: 7 * 2 + 1 + 1 - 5 = 11
// Sunday May 1   : 7 * 0 + 0 + 1 - 0 = 1
// Tuesday May 31 : 7 * 4 + 2 + 1 - 0 = 31
const {
  useState
} = React;

const App = () => {
  const today = new Date();
  const [currentMonth, setCurrentMonth] = useState(today.getMonth());
  const currentYear = today.getFullYear();
  
  const months = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec"
  ];
  const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

  const daysInMonth = (iMonth, iYear) => {
    return 32 - new Date(iYear, iMonth, 32).getDate();
  };

  const calculateDay = (weekIndex, dayIndex, offset) =>
    7 * weekIndex + dayIndex + 1 - offset;

  const renderCalendar = () => {
    const totalDays = daysInMonth(currentMonth, currentYear);
    const firstDayInMonth = new Date(currentYear, currentMonth).getDay();
    const totalWeeks = Math.ceil((firstDayInMonth+totalDays) / 7);

    return (
      <div className="table">
        <div className="row">
          {weekDays.map((e) => (
            <div className="column" key={`col${e}`}>
              {e}
            </div>
          ))}
        </div>
        {[...Array(totalWeeks)].map((w, wIx) => {
          return (
            <div className="row">
              {weekDays.map((d, dIx) => (
                <div className="column">
                  {(wIx === 0 && dIx < firstDayInMonth) ||
                  calculateDay(wIx, dIx, firstDayInMonth) > totalDays
                    ? ""
                    : calculateDay(wIx, dIx, firstDayInMonth)}
                </div>
              ))}
            </div>
          );
        })}
      </div>
    );
  };

  return (
    <div>
      <h1>Calendar Budget App</h1>
      <div>
        <select
          value={currentMonth}
          onChange={(e) => setCurrentMonth(e.target.value)}
        >
          {months.map((m, i) => (
            <option value={i}>{m}</option>
          ))}
        </select>
        <h2>
          {months[currentMonth]} {currentYear}
        </h2>
        {renderCalendar()}
      </div>
    </div>
  );
};

// Render it
ReactDOM.render( <App / > ,
  document.getElementById("root")
);
.App {
  font-family: sans-serif;
  text-align: center;
}

.table {
  margin: 48px 0;
  box-shadow: 0 5px 10px -2px #cfcfcf;
  font-family: Arial;
  display: table;
  width: 100%;
}

.row {
  display: table-row;
  min-height: 48px;
  border-bottom: 1px solid #f1f1f1;
}

.column {
  display: table-cell;
  border: 1px solid #f1f1f1;
  vertical-align: middle;
  padding: 10px 0;
}

.row:first-child {
  font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Leave a Comment