Is there a way I can synchronize data between the TimelineList and the Animated Scroll on top of the Calendar?

Am using react-native-calendars in a project based on the code below.As can be seen top component which is a scrollable based on data it receives.It works well.Also below it I have the Calendar with events.Right now I can swipe through the Calendar TimelineList and it works well.The callenge am having is how to sycnronize data between the two components.I want if I swipe through the Calendar TimelineList,data on top component also changes and vice versa

import React, { Fragment, useState, useEffect, useMemo } from "react";
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  SafeAreaView,
} from "react-native";
import {
  ExpandableCalendar,
  TimelineList,
  CalendarProvider,
  CalendarUtils,
} from "react-native-calendars";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withDecay,
  withSpring,
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { Feather, AntDesign, Entypo } from "@expo/vector-icons";

import { useNavigation } from "@react-navigation/native";
import { timelineEvents, getDate } from "../mocks/timelineEvents";
import groupBy from "lodash/groupBy";

const INITIAL_TIME = { hour: 9, minutes: 0 };
const INITIAL_DATE = "2023-01-01";
const EVENTS = timelineEvents;

const ScheduleTimeLine = () => {
  const navigation = useNavigation();
  const [scrollToFirst, setScrollToFirst] = useState(false);

  useEffect(() => {
    setScrollToFirst();
  }, []);

  const [isModalVisible, setModalVisible] = useState(false);
  const [selected, setSelected] = useState(INITIAL_DATE);

  const toggleModal = () => {
    setModalVisible(!isModalVisible);
  };

  const SCROLLABLE_WIDTH = 200;
  const animatedX = useSharedValue(0);
  const xContext = useSharedValue(0);
  const animStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: animatedX.value }],
    };
  });
  const panGesture = useMemo(
    () =>
      Gesture.Pan()
        .activeOffsetX([-17, 17])
        .activeOffsetY([-22, 22])
        .maxPointers(1)
        .onStart((e) => {
          xContext.value = animatedX.value;
        })
        .onUpdate((e) => {
          const target = xContext.value + e.translationX;
          if (target > 0) {
            animatedX.value = target * 0.3;
          } else if (target < -SCROLLABLE_WIDTH) {
            animatedX.value =
              -SCROLLABLE_WIDTH + (SCROLLABLE_WIDTH + target) * 0.3;
          } else {
            animatedX.value = target;
          }
        })
        .onEnd((e) => {
          const target = xContext.value + e.translationX;
          if (target > 0) {
            animatedX.value = withSpring(0, { mass: 0.3, stiffness: 110 });
          } else if (target < -SCROLLABLE_WIDTH) {
            animatedX.value = withSpring(-SCROLLABLE_WIDTH, {
              mass: 0.3,
              stiffness: 110,
            });
          } else {
            animatedX.value = withDecay({
              velocity: e.velocityX,
              clamp: [-SCROLLABLE_WIDTH, 0],
            });
          }
        }),
    []
  );

  const [currentDate, setCurrentDate] = useState(getDate());
  const [eventsByDate, setEventsByDate] = useState(
    groupBy(EVENTS, (e) => CalendarUtils.getCalendarDateString(e.start))
  );

  const marked = {
    [`${getDate(-1)}`]: { marked: true },
    [`${getDate()}`]: { marked: true },
    [`${getDate(1)}`]: { marked: true },
    [`${getDate(2)}`]: { marked: true },
    [`${getDate(4)}`]: { marked: true },
  };

  const timelineProps = {
    format24h: true,
    onBackgroundLongPress: createNewEvent,
    onBackgroundLongPressOut: approveNewEvent,
    unavailableHours: [
      { start: 0, end: 6 },
      { start: 22, end: 24 },
    ],
    overlapEventsSpacing: 8,
    rightEdgeSpacing: 24,
    animatedX
    };

  const onDateChanged = (date, source) => {
    console.log("TimelineCalendarScreen onDateChanged: ", date, source);
    setCurrentDate(date);
  };

  const onMonthChange = (month, updateSource) => {
    console.log("TimelineCalendarScreen onMonthChange: ", month, updateSource);
  };

  const createNewEvent = (timeString, timeObject) => {
    const { eventsByDate } = this.state;
    const hourString = `${(timeObject.hour + 1).toString().padStart(2, '0')}`;
    const minutesString = `${timeObject.minutes.toString().padStart(2, '0')}`;

    const newEvent = {
      id: 'draft',
      start: `${timeString}`,
      end: `${timeObject.date} ${hourString}:${minutesString}:00`,
      title: 'New Event',
      color: 'white'
    };

    if (timeObject.date) {
      if (eventsByDate[timeObject.date]) {
        eventsByDate[timeObject.date] = [...eventsByDate[timeObject.date], newEvent];
        this.setState({ eventsByDate });
      } else {
        eventsByDate[timeObject.date] = [newEvent];
        this.setState({ eventsByDate: { ...eventsByDate } });
      }
    }

  };

  const approveNewEvent = (_timeString, timeObject) => {
    const { eventsByDate } = this.state;
    
    Alert.prompt('New Event', 'Enter event title', [
      {
        text: 'Cancel',
        onPress: () => {
          if (timeObject.date) {
            eventsByDate[timeObject.date] = filter(eventsByDate[timeObject.date], e => e.id !== 'draft');

            this.setState({
              eventsByDate
            });
          }
        }
      },
      {
        text: 'Create',
        onPress: eventTitle => {
          if (timeObject.date) {
            const draftEvent = find(eventsByDate[timeObject.date], { id: 'draft' });
            if (draftEvent) {
              draftEvent.id = undefined;
              draftEvent.title = eventTitle || 'New Event';
              draftEvent.color="lightgreen";
              eventsByDate[timeObject.date] = [...eventsByDate[timeObject.date]];

              this.setState({
                eventsByDate
              });
            }
          }
        }
      }
    ]);

  };

  return (
    <Fragment>
      <View>
        <TouchableOpacity className="flex-row mb-2 w-36 h-10 ml-36">
          <Text className="mt-2 text-lg font-semibold">
            {new Date(selected).getMonth() + 1}-
            {new Date(selected).getFullYear()}
          </Text>
          <Entypo
            name="chevron-thin-down"
            size={20}
            color="black"
            style={{ marginLeft: 10, marginTop: 10 }}
          />
        </TouchableOpacity>
      </View>

      <GestureDetector gesture={panGesture}>
        <Animated.View style={{ marginVertical: 10 }}>
          <Animated.View
            style={[
              {
                flexDirection: "row",
                marginHorizontal: 10,
              },
              animStyle,
            ]}
          >
            {EVENTS.map((event, index) => (
              <TouchableOpacity
                key={index}
                className="items-center justify-center mx-1"
              >
                <View className="rounded-lg items-center justify-center border-2 border-gray-300 w-32 h-24">
                  <View className="w-12 h-12 bg-black rounded-full p-2 justify-center items-center ">
                    <Text className="text-lg text-white">
                      {event.title.charAt(0)}
                    </Text>
                  </View>
                  <Text className="text-xs mt-2 font-bold">{event.title}</Text>
                </View>
              </TouchableOpacity>
            ))}
          </Animated.View>
        </Animated.View>
      </GestureDetector>
      
        <CalendarProvider
        date={currentDate}
        onDateChanged={onDateChanged}
        onMonthChange={onMonthChange}
        showTodayButton={false}
        disabledOpacity={0.6}
      >
       
        <TimelineList
          events={eventsByDate}
          timelineProps={timelineProps}
          showNowIndicator
          scrollToFirst
          initialTime={INITIAL_TIME}
          style={{ height: 10 }}
        />
      </CalendarProvider>
    </Fragment>
  );
};

export default ScheduleTimeLine;

I have tried to modify the code like this to see if it works but it doesn’t

const timelineProps = {
    format24h: true,
    onBackgroundLongPress: createNewEvent,
    onBackgroundLongPressOut: approveNewEvent,
    unavailableHours: [
      { start: 0, end: 6 },
      { start: 22, end: 24 },
    ],
    overlapEventsSpacing: 8,
    rightEdgeSpacing: 24,
    animatedX
    };

I would appreciate if I can get the best approach to implement data synchronization between the two components

Leave a Comment