Vue.js3 render() function not letting me render multiple components in the same parent

50 Views Asked by At

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.

enter image description here

0

There are 0 best solutions below