简体   繁体   中英

react render for loop run twice

I am trying to make simple calendar with moment. It load perfect on first time but when click on previous or next my for loop in render run twice what can be the issue

My project https://stackblitz.com/edit/react-ts-itbzcd

    public render() {
        let day = this.props.date
        for (let i = 0; i < 7; i++) {
          this.days.push(
            <Day key={day.toString()} date={day.date()} />
          );
          day = day.clone();
          day.add(1, "day");
        }
    console.log(this.days)
    return (
      <tr key={this.days[0]}>
        {this.days}
      </tr>
    );
  }

Complete Calendar component:

import moment = require('moment');
import * as React from 'react'

import { DayNames, Week } from '.'
import styles from './Calendar.module.scss'

interface ICalendarProps {
}

interface ICalendarState {
  dateObject: moment.Moment
  showYearTable: boolean
}
export class Calendar extends React.Component<ICalendarProps, ICalendarState> {
  constructor(props: ICalendarProps) {
    super(props)
    this.state = {
      dateObject: moment(),
      showYearTable: false
    }

    this.onNext = this.onNext.bind(this)
    this.onPrev = this.onPrev.bind(this)
  }
  public render(): React.ReactElement<ICalendarProps> {
    const datePicker =
      <div className="datepicker-days">
        <table className={styles.table}>
          <thead>
            <tr>
              <th><span className="ms-Icon ms-Icon--ChevronLeft" title="Previous Month" onClick={this.onPrev}>Previous</span></th>
              <th className={styles["picker-switch"]} data-action="pickerSwitch" colSpan={5} title="Select Month">
                {this.month()}{" "} {this.year()}
              </th>
              <th><span className="ms-Icon ms-Icon--ChevronRight" title="Next Month" onClick={this.onNext}>Next</span></th>
            </tr>
            <tr>
              <DayNames />
            </tr>
          </thead>
          <tbody>
            {this.renderWeeks()}
          </tbody>
        </table>
      </div>

    return (
      <div className="datepicker">
        {datePicker}
      </div>
    )
  }

  private month = () => {
    return this.state.dateObject.format('MMMM')
  }
  private year = () => {
    return this.state.dateObject.format("Y");
  };
  private onPrev = () => {
    this.setState({
      dateObject: this.state.dateObject.subtract(1, this.state.showYearTable === true ? "year" : "month")
    });
  };
  private onNext = () => {
    this.setState({
      dateObject: this.state.dateObject.add(1, this.state.showYearTable === true ? "year" : "month")
    });
  };

  private renderWeeks() {
    let weeks = [];
    let date = this.state.dateObject.clone().startOf("month").add("w").day("Sunday");
    let done = false;
    let count = 0;
    let monthIndex = date.month();

    while (!done) {
      weeks.push(
        <Week key={date.toString()} date={date.clone()} />
      )
      date.add(1, "w");
      done = count++ > 2 && monthIndex !== date.month();
      monthIndex = date.month();
    }
    return weeks
  }
}

I got your problem. You are having this problem because of how you are setting the key prop of the Week component on the renderWeeks function. 1 week repeats from a month to another, so when you change the month, 2 Week s components end with the same key prop. React, when is rerendering your component, see that and recycle that component.

Doing something like this solves your problem:

private renderWeeks() {
    let weeks = [];
    let date = this.state.dateObject.clone().startOf("month").add("w").day("Sunday");
    let done = false;
    let count = 0;
    let monthIndex = date.month();

    while (!done) {
      weeks.push(
        <Week key={Math.floor(Math.random() * 10000)} date={date.clone()} />
      )
      date.add(1, "w");
      done = count++ > 2 && monthIndex !== date.month();
      monthIndex = date.month();
    }
    return weeks
  }

But it could decrease your rerendering performance, you need to deal with your Calendar component to avoid him to rerender the first week or you can clean all of your calendar states with a loading component (bad UX). There are many alternatives.

My solution would be storing the weeks on the Calendar component state, then update it with just the new weeks.

The loop in Week - execute 4 times, so keys in days duplicate.

[0,1,2,3,4,5,6] then [0,1,2,3,4,5,6] then [0,1,2,3,4,5,6] then [0,1,2,3,4,5,6]

So 21 days have duplicated key.


Ok, optimalized solution and basically works. (the problem appears when jumping between months) - do you need to write a condition that will check the number of days in the month and based on it stop the loops? You can solve this like this - loop in loop:

private renderWeeks() {
    let weeks = [];
    let date = this.state.dateObject.clone().startOf("month").add("w").day("Sunday");
    let count = 0;
    let monthIndex = date.month();
    let day = date;

    for (let i = 0; i < 4; i++) {
        weeks.push(<Week key={date.toString()} date={date.clone()} />)
        date.add(1, "w");
        monthIndex = date.month();
        for (let j = 0; j < 7; j++) {
            weeks.push(<Day key={weeks.length} date={day.date()} />);
            day = day.clone();
            day.add(1, "day");
        }
    }
    return weeks
}

And in Day.tsx

import * as React from 'react'

interface IDayProps {
    date: number
}


export class Day extends React.Component<IDayProps, {}> {

    public render() {
        return (
            <td>{this.props.date}</td>
        );
    }
}

You also can give for key :

<Day key={Math.random()} date={day.date()} />

It works optimally - the problem is:

  • cutting off months:
  • the 1st condition is "4" in 1st loop - should be calculate

在此处输入图片说明

It looks like the problem is in your renderWeeks() function in Calendar.tsx .

In the line done = count++ > 2 && monthIndex !== date.month(); , try moving the ++ to the front of count ( ++count ) so that it is incremented BEFORE being compared to 2 . Or reduce 2 to 1 .

StackBlitz wasn't working for me when I tried altering anything so I wasn't able to play around with it (pretty cool site though).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM