wmcmahan / react-native-calendar-events

📆 React Native Module for iOS and Android Calendar Events
MIT License
903 stars 291 forks source link

Removing many events causes them to sporadically reappear #184

Open coderdave opened 6 years ago

coderdave commented 6 years ago

When I remove and create many events (around 50) in quick succession (around 5 seconds apart), it sometimes doesn't delete the old events, but instead, just adds the new events. I don't get any errors removing the old events either.

Is there maybe some kind of limit to how many times you can hit the calendar API before it starts to ignore requests?

I've tried to be a bit "slower" in how I hit the calendar, for example, waiting until all the events are removed before I add them (with callbacks).

For some odd reason I can't reproduce this with the simulator, I have to use the app on my iPhone.

Environment

Environment: OS: macOS High Sierra 10.13.6 Node: 9.4.0 Yarn: 1.1.0 npm: 3.3.10 Watchman: 4.5.0 Xcode: Xcode 9.4.1 Build version 9F2000 Android Studio: 2.3 AI-162.4069837

Packages: (wanted => installed) react: 16.3.1 => 16.3.1 react-native: 0.55.3 => 0.55.3

Steps to Reproduce

(in quick succession)

  1. Add many events (around 50).
  2. Delete all of them.
  3. Add many more events.
  4. Notice it didn't delete the old events, but just added the new ones.

Here's the "deleteCalendarEvents" function:

export const deleteCalendarEvents = function deleteCalendarEvents(params) {
  let deletePromiseChain = [];
  let eventIds = params.eventIds;

  RNCalendarEvents.authorizeEventStore().then(fulfilled => {
    if (fulfilled === "authorized") {
      _.forEach(eventIds, (eventId) => {
        deletePromiseChain.push(
          RNCalendarEvents.removeEvent(eventId).then(event => {
            console.log('deleted event', event);
          })
          .catch(error => {
            console.log('delete event error', error);
          })
        );
      });

      Promise.all(deletePromiseChain).then(
        success => {
          if (params.callback) {
            params.callback();
          }
        },
        rejected => syncErrorAlert()
      );
    } else {
      authErrorAlert();
    }
  });
};

Expected Behavior

It removes and creates many events in quick succession without a problem.

Actual Behavior

It doesn't delete old events.

before after

coderdave commented 6 years ago

Update: I removed the creating of events function so now it just mass deletes many events. As I'm watching the iOS calendar, I see these events disappear and then all of a sudden reappear on the calendar!

wmcmahan commented 6 years ago

Hey @coderdave, I'm curious are you saving to an external calendar, i.e. Google, on both simulator and physical device? I ask because I have experienced phantom events from delayed syncing.

coderdave commented 6 years ago

The simulator is just using it's local "Calendar" and I'm not having any issues there. The issue is on my physical device and I'm not using an external calendar like Google. I'm just using a calendar named "Calendar". Does having it on iCloud mess with anything?

coderdave commented 6 years ago

@wmcmahan I created a quick and dirty component to test this out. The higher the number of events, the more likely you'll see the issue. For example, if it's around 10 events, then I don't see it happening, But around 50 or so, it's pretty consistent. (As a reminder, everything is fine on the simulator, it's only my physical device where the issues occurs, I tried both my iPhone and my iPad)

So again, to reproduce:

  1. create X number of events
  2. delete X number of events
  3. notice the events pop back into the calendar (you might need to wait for about 30 seconds)
import React from 'react';
import {
  Button,
  View,
  Alert,
  TextInput,
  StyleSheet,
  Keyboard,
} from 'react-native';

import RNCalendarEvents from 'react-native-calendar-events';
const moment = require('moment-timezone');

const styles = StyleSheet.create({
  input: {
    color: '#000000',
    fontSize: 16,
    height: 36,
    padding: 7,
    borderRadius: 4,
    borderColor: '#cccccc',
    borderWidth: 1,
  },
});

export default class CalendarEventsTest extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      numEvents: 50,
      eventIds: []
    };
  }

  createCalendarEvents = () => {
    Keyboard.dismiss();

    let createPromiseChain = [];
    let startDate = moment().toISOString();
    let endDate = moment().add(1, "hour").toISOString();

    RNCalendarEvents.authorizeEventStore().then(fulfilled => {
      if (fulfilled === "authorized") {
        for (let i = 0; i < this.state.numEvents; i++) {
          createPromiseChain.push(
            RNCalendarEvents.saveEvent("Popeye", {startDate, endDate})
          );
          startDate = moment(startDate).add(1, "day").toISOString();
          endDate = moment(startDate).add(1, "hour").toISOString();
        }

        Promise.all(createPromiseChain).then(
          (eventIds) => {
            console.log('events created', eventIds);
            Alert.alert(null, `successfully created ${this.state.numEvents} events`);
            this.setState({
              eventIds: eventIds
            });
          },
          (rejected) => {
            Alert.alert(null, 'createCalendarEvents error ' + rejected);
          }
        );
      } else {
        Alert.alert(null, 'not authorized');
      }
    });
  };

  deleteCalendarEvents = () => {
    Keyboard.dismiss();

    let deletePromiseChain = [];

    RNCalendarEvents.authorizeEventStore().then(fulfilled => {
      if (fulfilled === "authorized") {
        for (let i = 0; i < this.state.eventIds.length; i++) {
          deletePromiseChain.push(
            RNCalendarEvents.removeEvent(this.state.eventIds[i]).then(event => {
              console.log('deleted event', event);
            })
          );
        }

        Promise.all(deletePromiseChain).then(
          success => {
            Alert.alert(null, `successfully deleted ${this.state.eventIds.length} events`);
            this.setState({
              eventIds: []
            })
          },
          rejected => {
            Alert.alert(null, 'deleteCalendarEvents error' + rejected);
          }
        );
      } else {
        Alert.alert(null, 'not authorized');
      }
    });
  };

  render() {
    return(
      <View style={{paddingTop: 100}}>
        <Button title="back" onPress={this.props.navigator.pop} />
        <TextInput style={styles.input} onChangeText={(value) => this.setState({numEvents: value})}
          value={`${this.state.numEvents}`} keyboardType="numeric" clearButtonMode="while-editing" />
        <Button title={`create ${this.state.numEvents} events`} onPress={this.createCalendarEvents} />
        <Button title={`delete ${this.state.eventIds.length} events`} onPress={this.deleteCalendarEvents} />
      </View>
    );
  }
}
coderdave commented 6 years ago

Update: This happens on Android also.

coderdave commented 6 years ago

@wmcmahan Are you able to give a status update? Any workarounds I can do in the mean time?

wmcmahan commented 6 years ago

@coderdave - I've tried reproducing this by creating 100 events on both iCloud, Gmail, and local calendars on my physical devices and everything deleted successfully without reappearing. I also tried with recurring and nonrecurring events, all with successful deletes.

The only other thing that I could imagine causing ghost deletes would be trying to edit/delete an event on a calendar that doesn't allow modifications.

coderdave commented 6 years ago

@wmcmahan I tried it on another iPhone and the same bug happened. The deleted events reappear. Are you trying the component I created? If not, can you send the code you're using?

wmcmahan commented 6 years ago

I created 100 events.

for (let i = 0; i < 100; i++) {
   RNCalendarEvents.saveEvent('Test event', {startDate: <startDate>, endDate: <endDate>})
}

Then simply deleted them

RNCalendarEvents.fetchAllEvents(<startDate>, <endDate>).then(events => {
  for (let event of events) {
    RNCalendarEvents.removeEvent(event.id)
  }
})
coderdave commented 6 years ago

@wmcmahan That's a bit different from what I did. For instance, I'm not usingfetchAllEvents. Are you able to please try the example I submitted?

Vizards commented 6 years ago

@wmcmahan

I think I met the similar issue, I found that when I set the saveEvent option like this:

await RNCalendarEvents.saveEvent(title, {
          startDate,
          endDate,
          recurrenceRule: {
            frequency: 'weekly',
            occurrence,
            interval,
          },
          location,
          notes: 'my notes',
          alarms: [{
            date: -5,
          }]
        }, {
          futureEvents: true,
        });

It only happens when I want to save more than one event. I used forEach to traverse and save different events.

If I removed recurrenceRule, location and alarms, it works fine. Can you reproduce this issue?

p.s. My React-Native version is 0.52.0. My react-native-calendar-events version is 1.6.3. Running on iOS 11.4 Simulator.

coderdave commented 6 years ago

As an experiment, I delayed each delete by a second to see if that helped. Interestingly, it helped a little, but some events still popped back. I wonder how hard it would be to have removeEvent take an array of id's to delete? (basically a "bulk delete")

I'm also wondering why I seem to be the only one running into this... Either developers aren't mass deleting or there's just some fundamental flaw with my code I haven't seen yet. This is depressing, this totally blocked a whole feature from being released. :(

Chandrasekar-G commented 5 years ago

@coderdave @Vizards I face a similar issue too. The difference is I used saveEvent instead of delete in your case. It doesn't occur in android and happens only for iOS.

The temporary fix I came up with is to not use the location parameter while saving an event. With just the title, start and endDate it works fine for iOS.

Will try to look into the native code and create a pull request when I find the time. @coderdave thanks for opening the issue - Otherwise we would never have thought of this solution.

coderdave commented 5 years ago

Thanks @Chandrasekar-G, but I'm not even using the location param. In my test code above, all I'm doing is RNCalendarEvents.saveEvent("Popeye", {startDate, endDate}).

JayantJoseph commented 5 years ago

I met with a similar issue, as said by @Vizards and @Chandrasekar-G . I created lots of events (in a loop) with recurrence rule passed. The issue was not found in Android, but resulted in crash in iOS. Single creations of events are not causing any problems (with and without recurrence rule). Only when a lot of events are created at once(with recurrence) does this problem arise (in iOS). Without recurrence rule, it works fine in both android and iOS.

P.S: @wmcmahan It would be an awesome update if we can pass an array of dates(with all the options) to create/delete events. It would also help a lot if we can pass an array of exception dates as well! Another update would be an option to pass the rrule string directly! Hope you put these features in your library and make it awesome, and fix this issue, or help us with a workaround!

JayantJoseph commented 5 years ago

I think the solution for this issue is this pull request by @LorienHW ! https://github.com/wmcmahan/react-native-calendar-events/pull/220

jmagdada commented 5 years ago

I'm currently also facing this issue - saving a lot of events makes the app crash.

@JayantJoseph should we wait for a new version to get the fix from @LorienHW? sorry I'm new to this. :)

coderdave commented 5 years ago

@jmagdada I tried that PR from @LorienHW and it actually made things worse for me. When I removed all of the events, it left a dot on the date but nothing is there. If I zoom into the day, it says "New Event" and when I try to delete it, the calendar app crashes. It looks like it's not assigned to a specific calendar, that may be why.

JayantJoseph commented 5 years ago

@LorienHW s pull request works for me, with minor changes in that page!

jmagdada commented 5 years ago

@JayantJoseph & @coderdave what should I do so I can try the pull request too? sorry for the stupid question. I do not know how. :D

coderdave commented 5 years ago

@JayantJoseph It works in the simulator (as it always has) but it doesn't work on a physical device for me (my iOS phone). Have you tried the test code in this comment? https://github.com/wmcmahan/react-native-calendar-events/issues/184#issuecomment-412214040

coderdave commented 5 years ago

@jmagdada There's probably a better way, but I just made sure I have the latest version of this library and grab the changes that were made in the PR and copy them to my node_modules/react-native-calendar-events directory. The only thing that was changed in that PR was https://raw.githubusercontent.com/wmcmahan/react-native-calendar-events/a7d8ef51c842b944011d478d7e003fbabe829443/RNCalendarEvents.m

So I copied the entire contents of that file and overwrote the entire contents of that file that I have locally.

jmagdada commented 5 years ago

Thank you @coderdave I will try that.

LorienHW commented 5 years ago

@jmagdada Our fork is up to date with master in the base fork, so to try out our patch you can also set your npm dependency in your package.json file to "react-native-calendar-events": "github:Outpatient/react-native-calendar-events#a7d8ef51c842b944011d478d7e003fbabe829443",. In my experience, you may need to first run npm uninstall react-native-calendar-events to remove an existing version to avoid caching issues.

jmagdada commented 5 years ago

Thank you @LorienHW

ConnectedReasoning commented 5 years ago

I am experiencing this problem with 1.7.3 I am deleting the events from my apple calendar(specifically the one I am using for RNCalendarEvents) and yet when i run a RNCalendarEvents.fetchAllEvents I am still seeing these events when I write them to console.log.

In addition, if I add events to the a specific calendar, I cannot see those events in when I refer to them with the RNCalendarEvents.fetchAllEvent method.