react-native-calendars: Agenda : "shouldComponentUpdate" is not working properly

Description

Hi, When the name of an item for a day has changed (other than the first), the reservation component does not trigger the update.

Expected Behavior

Trigger update for all changed items.

Suggested fix

I think the issue came from : reservation We see that we say do NOT update the reservation if the d1/d2 does not exist. And we know that d1/d2 is a date that only exists for the first reservation item. That’s why I think only my first item is updated.

  shouldComponentUpdate(nextProps: ReservationProps) {
    const d1 = this.props.date;
    const d2 = nextProps.date;
    const r1 = this.props.item;
    const r2 = nextProps.item;
    
    let changed = true;
    if (!d1 && !d2) { 
      changed = false; // HERE
    } else if (d1 && d2) {
      if (d1.getTime() !== d2.getTime()) {
        changed = true;
      } else if (!r1 && !r2) {
        changed = false;
      } else if (r1 && r2) {
        if ((!d1 && !d2) || (d1 && d2)) {
          if (isFunction(this.props.rowHasChanged)) {
            changed = this.props.rowHasChanged(r1, r2);
          }
        }
      }
    }
    return changed;
  }

Environment

  • npm ls react-native-calendars: react-native-calendars@1.1280.0
  • npm ls react-native: react-native@0.66.4

Device/emulator/simulator & OS version: Android / Galaxy A12 / Android version : 11

Reproducible Demo

I took the example of the demo by adding an action which should modify the name of the clicked item by “Has changed !” Below the code :

import React, {Component} from 'react';
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import {Agenda, AgendaEntry, AgendaSchedule, DateData} from 'react-native-calendars';

interface State {
  items?: AgendaSchedule;
}

export default class AgendaScreen extends Component<State> {
  state: State = {
    items: undefined,
  };

  render() {
    return (
      <Agenda
        reservationsKeyExtractor={item => `${item.reservation?.day}`}
        items={this.state.items}
        loadItemsForMonth={this.loadItems}
        selected={'2017-05-16'}
        renderItem={this.renderItem}
        renderEmptyDate={this.renderEmptyDate}
        rowHasChanged={this.rowHasChanged}
        showClosingKnob={true}
        // markingType={'period'}
        // markedDates={{
        //    '2017-05-08': {textColor: '#43515c'},
        //    '2017-05-09': {textColor: '#43515c'},
        //    '2017-05-14': {startingDay: true, endingDay: true, color: 'blue'},
        //    '2017-05-21': {startingDay: true, color: 'blue'},
        //    '2017-05-22': {endingDay: true, color: 'gray'},
        //    '2017-05-24': {startingDay: true, color: 'gray'},
        //    '2017-05-25': {color: 'gray'},
        //    '2017-05-26': {endingDay: true, color: 'gray'}}}
        // monthFormat={'yyyy'}
        // theme={{calendarBackground: 'red', agendaKnobColor: 'green'}}
        //renderDay={(day, item) => (<Text>{day ? day.day: 'item'}</Text>)}
        // hideExtraDays={false}
        // showOnlySelectedDayItems
      />
    );
  }

  loadItems = (day: DateData) => {
    const items = this.state.items || {};

    setTimeout(() => {
      for (let i = -15; i < 85; i++) {
        const time = day.timestamp + i * 24 * 60 * 60 * 1000;
        const strTime = this.timeToString(time);

        if (!items[strTime]) {
          items[strTime] = [];

          const numItems = 3;
          for (let j = 0; j < numItems; j++) {
            items[strTime].push({
              name: 'Item for ' + '#' + j,
              height: Math.max(50, Math.floor(Math.random() * 150)),
              day: strTime + j,
            });
          }
        }
      }

      const newItems: AgendaSchedule = {};
      Object.keys(items).forEach(key => {
        newItems[key] = items[key];
      });
      this.setState({
        items: newItems,
      });
    }, 1000);
  };

  updateItem = (id: string) => {
    const newItems: AgendaSchedule = {...this.state.items};
    if (this.state.items) {
      Object.keys(this.state.items).forEach(key => {
        newItems[key] = this.updateAgendaEntry(key, id);
      });
      this.setState({
        items: newItems,
      });
    }
  };
  updateAgendaEntry = (date: string, id: string) => {
    let agendaEntryToUpdate: AgendaEntry[] = Object.assign([], this.state.items?.[date]);
    let index = agendaEntryToUpdate.findIndex(a => a.day === id);

    agendaEntryToUpdate[index] = Object.assign(
      {},
      {
        ...agendaEntryToUpdate[index],
        name: 'Has changed !',
      }
    );
    return agendaEntryToUpdate;
  };
  renderItem = (reservation: AgendaEntry, isFirst: boolean) => {
    const fontSize = isFirst ? 16 : 14;
    const color = isFirst ? 'black' : '#43515c';

    return (
      <TouchableOpacity style={[styles.item, {height: reservation.height}]} onPress={() => this.updateItem(reservation.day)}>
        <Text style={{fontSize, color}}>{reservation.name}</Text>
      </TouchableOpacity>
    );
  };

  renderEmptyDate = () => {
    return (
      <View style={styles.emptyDate}>
        <Text>This is empty date!</Text>
      </View>
    );
  };

  rowHasChanged = (r1: AgendaEntry, r2: AgendaEntry) => {
    return r1.name !== r2.name;
  };

  timeToString(time: number) {
    const date = new Date(time);
    return date.toISOString().split('T')[0];
  }
}

const styles = StyleSheet.create({
  item: {
    backgroundColor: 'white',
    flex: 1,
    borderRadius: 5,
    padding: 10,
    marginRight: 10,
    marginTop: 17,
  },
  emptyDate: {
    height: 15,
    flex: 1,
    paddingTop: 30,
  },
});

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 6
  • Comments: 19

Commits related to this issue

Most upvoted comments

I seem to have a similar problem here. Agenda does not rerender when items changes.

Same as #1589

Any updates on the issue? Patched the library for now, but it would be nice to see this fixed.

That’s what I did, but it doesn’t make sense. The main feature of the library is not working. Do I need to send the node modules folder to git as well. After all, I am making changes there. getting harder to use

You should use patch-package to patch this. Easier to maintain your changes by keeping your changes when you remove your node module folder and auto merge as much as possible when you update react-native-calendars