react-native-calendars icon indicating copy to clipboard operation
react-native-calendars copied to clipboard

Agenda : "shouldComponentUpdate" is not working properly

Open RimApp opened this issue 2 years ago • 20 comments

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

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,
  },
});

RimApp avatar Apr 02 '22 13:04 RimApp

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

DianaLaa avatar Apr 04 '22 15:04 DianaLaa

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

Letty avatar Apr 26 '22 12:04 Letty

Same as #1589

CarlosJJR avatar Apr 27 '22 23:04 CarlosJJR

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jul 31 '22 08:07 stale[bot]

Any update on this?

francesco-clementi-92 avatar Aug 31 '22 17:08 francesco-clementi-92

I fixed it by patching shouldComponentUpdate in this way:

shouldComponentUpdate(nextProps) {
        const d1 = this.props.date;
        const d2 = nextProps.date;
        const r1 = this.props.item;
        const r2 = nextProps.item;

        let changed = true;
        if (this.props.rowHasChanged(r1, r2)) { // added this condition
            changed = true;
        } else if (!d1 && !d2) {
            changed = false;
        } 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;
    }

but this works if you always define the rowHasChanged function. I could check if exist the function before, but it has been written in the bottom for performance reasons maybe?

francesco-clementi-92 avatar Nov 11 '22 20:11 francesco-clementi-92

Any updates on the issue?

ali27001 avatar Dec 20 '22 12:12 ali27001

Nop I made a patch

RimApp avatar Dec 20 '22 15:12 RimApp

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

ali27001 avatar Dec 21 '22 06:12 ali27001

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

RimApp avatar Dec 21 '22 08:12 RimApp

Any alternatives to using patch-package? Is there any reason to actually have this function in the package? The fact that it stops users from seeing updated state makes it pretty useless

maximilian avatar Mar 07 '23 20:03 maximilian

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jun 11 '23 09:06 stale[bot]

how to fix this problem

Delocy avatar Jun 20 '23 09:06 Delocy

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Oct 15 '23 08:10 stale[bot]

The issue has not been fixed

francesco-clementi-92 avatar Dec 12 '23 10:12 francesco-clementi-92

#2189 When this merge will be released you just need to specify the rowHasChanged prop and it will update the component

francesco-clementi-92 avatar Dec 15 '23 07:12 francesco-clementi-92

+1

Polad10 avatar Jan 25 '24 20:01 Polad10

Any update on this? It's almost two years that there is this bug and the pull request wasn't approve

francesco-clementi-92 avatar Feb 29 '24 11:02 francesco-clementi-92

My PR has not been accepted. Any support here? I have a patch package that I want to delete. How is it even possible that a bug like this stays open for years?

francesco-clementi-92 avatar Apr 22 '24 09:04 francesco-clementi-92