I'm trying to make a Google Calendar app clone using Vue.js 3
The issue I'm facing is that whenever I try to render a new Event component using Vue.js render() and h() functions inside its parent element, only the previous Event component gets updated (because I change its props).
Calendar.vue (parent)
<script setup lang="ts">
import { ref, onMounted, computed, watch, h, render, reactive } from "vue";
import Event from "@components/Event.vue";
export interface IEvent {
id: number;
title: string;
description?: string;
startDate: Date | null;
endDate: Date | null;
color?: string;
}
const CELL_HEIGHT = 40;
const eventElementStyles = ref({
position: "absolute",
width: "",
height: CELL_HEIGHT + "px",
backgroundColor: "rgb(141, 224, 141)",
zIndex: "100",
top: "",
left: "",
});
const columnElementRefs = ref<HTMLDivElement[] | null>(null);
const isClicked = ref<boolean>(false);
const indexOfClickedColumn = ref<number>(-1);
const newEvent = ref<IEvent>({
id: 1,
title: "New Event Title",
description: "New Event Description",
startDate: null,
endDate: null,
color: "rgb(141, 224, 141)",
});
function resetStyles() {
eventElementStyles.value = {
position: "absolute",
width: "",
height: CELL_HEIGHT + "px",
backgroundColor: "rgb(141, 224, 141)",
zIndex: "100",
top: "",
left: "",
};
}
function resetNewEvent() {
newEvent.value = {
id: 1,
title: "New Event Title",
description: "New Event Description",
startDate: null,
endDate: null,
color: "rgb(141, 224, 141)",
};
}
function renderComponent(component, parent, props) {
console.log(props);
const vueComponent = h(component, props);
render(vueComponent, parent);
}
function handleMousedown(
startDate: Date,
index: number,
rowIndex: number
): void {
indexOfClickedColumn.value = index;
isClicked.value = true;
resetNewEvent();
resetStyles();
newEvent.value.startDate = startDate;
newEvent.value.endDate = new Date(startDate.getTime() + 60 * 60_000);
eventElementStyles.value.width = `180.58px`;
eventElementStyles.value.top = `${rowIndex * CELL_HEIGHT}px`;
renderComponent(Event, columnElementRefs.value![index], {
styles: eventElementStyles.value,
event: newEvent.value,
});
}
function isPartiallyResizable(colIndex: number): boolean {
return isClicked.value && colIndex === indexOfClickedColumn.value;
}
function canExtendEventTime(colIndex: number, cellDate: Date): boolean {
return (
isPartiallyResizable(colIndex) &&
cellDate.getTime() >= newEvent.value.endDate!.getTime()
);
}
function canShrinkEventTime(colIndex: number, cellDate: Date): boolean {
return (
isPartiallyResizable(colIndex) &&
cellDate.getTime() < newEvent.value.endDate!.getTime()
);
}
function getIncreasedEventHeight(
oldHeight: number,
increaseBy: number
): number {
return oldHeight + increaseBy;
}
function getDecreasedEventHeight(
oldHeight: number,
decreaseBy: number
): number {
return oldHeight - decreaseBy;
}
function handleMouseover(colIndex: number, cellDate: Date): void {
// increase event element height
if (canExtendEventTime(colIndex, cellDate)) {
newEvent.value.endDate = new Date(
newEvent.value.endDate!.getTime() + 60 * 60_000
);
eventElementStyles.value.height =
getIncreasedEventHeight(
Number(eventElementStyles.value.height.split("px")[0]),
CELL_HEIGHT
) + "px";
return;
}
// decrease event time
if (canShrinkEventTime(colIndex, cellDate)) {
newEvent.value.endDate = new Date(
newEvent.value.endDate!.getTime() - 60 * 60_000
);
eventElementStyles.value.height =
getDecreasedEventHeight(
Number(eventElementStyles.value.height.split("px")[0]),
CELL_HEIGHT
) + "px";
return;
}
}
function handleMouseup(endDate: Date): void {
isClicked.value = false;
}
</script>
Event.vue (Child)
<template>
<div :style="styles" class="eventContainer">
<span class="title">(No title) </span>
<span class="times">{{ time }}</span>
</div>
</template>
<script setup lang="ts">
import { IEvent } from "@/pages/Calendar.vue";
import { computed } from "vue";
const { styles, event } = defineProps<{
styles: any;
event: IEvent;
}>();
const time = computed(() => {
return `${extractTimeFromDate(event.startDate!)} - ${extractTimeFromDate(
event.endDate!
)}`;
});
function extractTimeFromDate(date: Date): string {
return `${date.getHours().toString().padStart(2, "0")}:${date
.getMinutes()
.toString()
.padStart(2, "0")}`;
}
</script>
I think the issue is that I'm always passing the same props (eventElementStyles and event). But why it only works when I render the component in different parent elements and when I try to render multiple Event components in the same parent, only the old one gets updated.
