react-native-calendars
react-native-calendars copied to clipboard
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
: [email protected] -
npm ls react-native
: [email protected]
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,
},
});
I seem to have a similar problem here. Agenda
does not rerender when items
changes.
Any updates on the issue? Patched the library for now, but it would be nice to see this fixed.
Same as #1589
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.
Any update on this?
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?
Any updates on the issue?
Nop I made a patch
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
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
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
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.
how to fix this problem
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.
The issue has not been fixed
#2189 When this merge will be released you just need to specify the rowHasChanged prop and it will update the component
+1
Any update on this? It's almost two years that there is this bug and the pull request wasn't approve
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?