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