Search code examples
reactjsreact-hookssupabasedayjs

How to isolate a function within a mapped JSX element?


Problem Description:

I am trying to within a Calendar component, to isolate the response from a function in each of the mapped 'Calendar Day Cells', in order to define the 'css' variant that should be used, but when I call the function it leads to a too many re-renders (infinite loop).

For this specific scenario, I'm using Stitches for Styling, which is what I've used to define the variants.

Supabase as a Database

Dayjs to handle dates

Below is an extract of the styling with Stitches

variants: {
    variant: {
      default: {
        color: "#303A46",

        'svg': {
          display: 'none',
        },

        'div:nth-child(2)': {
          display: 'none',
        },
        'div:nth-child(3)': {
          display: 'none',
        },
      },
      reserved: {
        color: "#374758",
        backgroundColor: '#E9BFB5',

        'svg': {
          display: 'none',
        },

        'div:nth-child(2)': {
          display: 'none',
        },
        'div:nth-child(3)': {
          display: 'none',
        },
      },

And here is the part in the which I make the evaluations for the variant of the jsx element, (I included the entire calendar element for more clarity on the structure)

<Styles.CalendarHeaderWrapper>
    <h3>{selectedDate.clone().format("MMMM, YYYY")}</h3>
    <div>
      <CircledLeft
        onClick={monthSubBoundary} />
      <CircledNavigation
        onClick={monthAddBoundary} />
    </div>
</Styles.CalendarHeaderWrapper>
  <Styles.WeekDaysWrapper>
    {generateWeeksOfTheMonth[0].map((day, index) => (
      <Styles.WeekDayCell key={`week-day-${index}`}>
        {dayjs(day).format(windowsize.width < 1600 ? 'ddd' : 'dddd')}
      </Styles.WeekDayCell>
    ))}
  </Styles.WeekDaysWrapper>
      {generateWeeksOfTheMonth.map((week, weekIndex) => (
        <Styles.CalendarContentWrapper key={`week-${weekIndex}`}>
          {week.map((day, dayIndex) => (
            <Styles.CalendarDayCell
              key={`day-${dayIndex}`}
              onClick={() => handleClick(day)}
              onDoubleClick={() => handleDoubleClick(day)}
              variant={setDayCellVariant(day)}
                >
                  {day.getDate()}
                  <HalfDay />
                  <div></div>
                  <div></div>
                </Styles.CalendarDayCell>
              ))}
        </Styles.CalendarContentWrapper>

I'm also using Supabase to fetch some 'reservations' data that I later (intend to) use to match with the date within each of the 'Calendar Day Cells' in order to set the variant to something like 'reserved'

Here is the async function that gets the data

useEffect(() => {
    getPublicReservations()
  }, [firstDayOfFirstWeekOfMonth])

  const [supabaseloaded, setSupabaseLoaded] = useState(false);

  const getPublicReservations = async () => {
    try {
      let {data, error, status} = await supabase
        .from('reservations_preprocess')
        .select('id, in_between_dates')

      if (error && status !== 406) {
        throw error
      }

      if (data) {
        setReservationsData(data);
      }
    }
    catch (error) {
      alert(error.message)
    }
    finally {
      setSupabaseLoaded(true);
    }
  }

For the function I'm using a for loop to go through each of the reservation entries and match the dates (with an if conditional) and then ideally if true to set the variant to reserved for 'that specific day cell' and ideally to run this only when some other condition changes, like the activeMonth or the selectedDate.

I had though something like this, and then to pass it to the variant tag, but that's what led me to the too many re-renders issue:

 const setDayCellVariant = useCallback(day => {
    if (supabaseloaded === true) {
      for (let i = 0; i < reservationsdata.length; i++) {
        if (dayjs(day).format('YYYY-MM-DD') === reservationsdata[i].in_between_dates) {
          handleHookCall();
        }
    }
    }
    return variant
  }, [supabaseloaded])

The call to the useState hook to 'setVariant' to the desired value:

 const handleHookCall = () => {
    setVariant('reserved')
  }

The setDayCellVariant would be called in the variants tag of the JSX element like so

variant={setDayCellVariant(day)} 

For a more visual idea of what I'm trying to achieve you can refer to this:

Calendar Design

Thank you in advance!


Solution

  • You use a hook during a render, dont do that. The setState will trigger a rerender. I cant se the state, however it looks like they all share the same variant. Change the function to just return the variant instead of setting at state that you do not need and use otherwise.

    const setDayCellVariant = useCallback(
      (day) => {
        return supabaseloaded &&
          reservationsdata.some(
            (d) => d.n_between_dates === dayjs(day).format("YYYY-MM-DD")
          )
          ? "reserved"
          : "dafault type";
      },
      [supabaseloaded, reservationsdata]
    );