'React function executes continuously and state keeps refreshing

I have kind of a weird issue in React. I am using the Blitz JS framework along side with Prisma for database stuff. I have a function that queries the database for all entries from a date the user selects forward. It's used for a reservation system that I am trying to build. After I get the data I use it to create a <select> element and set as <option> every space that does not appear in the database. Everything works fine, the <select> and <option> display what they should, but as soon as I click the drop-down to see all available option, I think the state refreshes and the menu closes down. If I console.log() inside the function, it will go on forever in the console menu. Also in the terminal I can see the function being called about every second or so.

terminal log
javascript console log

I also tried quering the DB from a useEffect() but useEffect() and useQuery (from Blitz.js) don't work together

I will attach the code along side with comments for easier reading. Thank you for your time!

Main Page

import { BlitzPage, invoke, useQuery } from "blitz"
import { useState, useEffect, Suspense } from "react"
import { UserInfo } from "app/pages"
import DatePicker from "react-datepicker"
import "react-datepicker/dist/react-datepicker.css"
import addDays from "date-fns/addDays"
import format from "date-fns/format"
import insertBooking from "app/bookings/mutations/insertBooking"
import getAllBookings from "app/bookings/queries/getAllBookings"
import { useCurrentBookings } from "app/bookings/hooks/useCurrentBookings"
import { useCurrentUser } from "app/core/hooks/useCurrentUser"

const Add: BlitzPage = () => {
  //State for all options that will be added for the booking
  const [state, setState] = useState({
    intrare: 1,
    locParcare: 0,
    locPescuit: 0,
    casuta: 0,
    sezlong: 0,
    sedintaFoto: false,
    petrecerePrivata: false,
    totalPrice: 20,
  })
  //Date state added separately
  const [startDate, setStartDate] = useState(addDays(new Date(), 1))

  const [availableSpots, setAvailableSpots] = useState({
    pescuit: [0],
    casute: {},
    sezlonguri: {},
  })

  // The function that reads the DB, manipulates the data so I can have
  // an array of open spots and then renders those values in a select
  const PescuitSelect = () => {
    const totalFishingSpots = Array.from(Array(114).keys())

    const bookings = useCurrentBookings(startDate) //useCurrentBookings is a hook I created

    const availableFishingSpots = totalFishingSpots.filter(
      (o1) => !bookings.some((o2) => o1 === o2.loc_pescuit)
    )
    console.log(availableFishingSpots)
    setAvailableSpots({ ...availableSpots, pescuit: availableFishingSpots })

    return (
      <select>
        {availableSpots.pescuit.map((value) => {
          return (
            <option value={value} key={value}>
              {value}
            </option>
          )
        })}
      </select>
    )
  }

  // Date state handler
  const handleDate = (date) => {
    setStartDate(date)
  }

  // Update the price as soon as any of the options changed
  useEffect(() => {
    const totalPrice =
      state.intrare * 20 +
      state.locParcare * 5 +
      (state.casuta ? 100 : 0) +
      (state.locPescuit ? 50 : 0) +
      (state.sedintaFoto ? 100 : 0) +
      state.sezlong * 15

    setState({ ...state, totalPrice: totalPrice })
  }, [state])

  type booking = {
    starts_at: Date
    ends_at: Date
    intrare_complex: number
    loc_parcare: number
    loc_pescuit: number
    casuta: number
    sezlong: number
    sedinta_foto: boolean
    petrecere_privata: boolean
    total_price: number
  }

  // Here I handle the submit. "petrecerePrivata" means a private party. If that is checked
  // it does something, if not, something else
  function handleSubmit(event) {
    event.preventDefault()
    if (state.petrecerePrivata === true) {
      setState({
        ...state,
        intrare: 0,
        locParcare: 0,
        locPescuit: 0,
        casuta: 0,
        sezlong: 0,
        sedintaFoto: false,
        totalPrice: 100,
      })
    } else {
      const booking: booking = {
        starts_at: startDate,
        ends_at: addDays(startDate, 1),
        intrare_complex: state.intrare,
        loc_parcare: state.locParcare,
        loc_pescuit: state.locPescuit,
        casuta: state.casuta,
        sezlong: state.sezlong,
        sedinta_foto: state.sedintaFoto,
        petrecere_privata: state.petrecerePrivata,
        total_price: state.totalPrice,
      }

      invoke(insertBooking, booking) // Insert the new created booking into the database
    }
  }

  // State handler for everything but the price, that updates in the useEffect
  const handleChange = (evt) => {
    const name = evt.target.name
    const value = evt.target.type === "checkbox" ? evt.target.checked : evt.target.value
    setState({
      ...state,
      [name]: value,
    })
  }

  return (
    <>
      <Suspense fallback="Loading...">
        <UserInfo />
      </Suspense>

      {
        // Here starts the actual page itself
      }

      <div className="mx-auto max-w-xs ">
        <div className="my-10 p-4 max-w-sm bg-white rounded-lg border border-gray-200 shadow-md sm:p-6 lg:p-8 dark:bg-gray-800 dark:border-gray-700">
          <form className="space-y-6" action="#" onSubmit={handleSubmit}>
            <h5 className="text-xl font-medium text-gray-900 dark:text-white">
              Fa o rezervare noua
            </h5>
            {state.petrecerePrivata ? (
              <>
                <div>
                  <label
                    htmlFor="date"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Alege Data
                  </label>
                  <div className="border-2 rounded">
                    <DatePicker
                      selected={startDate}
                      onChange={(date) => handleDate(date)}
                      dateFormat="dd/MM/yyyy"
                      includeDateIntervals={[{ start: new Date(), end: addDays(new Date(), 30) }]}
                      className="cursor-pointer p-2"
                    />
                  </div>
                </div>
                <label
                  htmlFor="checked-toggle"
                  className="relative inline-flex items-center mb-4 cursor-pointer"
                >
                  <input
                    type="checkbox"
                    name="petrecerePrivata"
                    id="checked-toggle"
                    className="sr-only peer"
                    checked={state.petrecerePrivata}
                    onChange={handleChange}
                  />

                  <div className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
                  <span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
                    Petrecere Privata
                  </span>
                </label>
              </>
            ) : (
              <>
                <div>
                  <label
                    htmlFor="date"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Alege Data
                  </label>
                  <div className="border-2 rounded">
                    <DatePicker
                      selected={startDate}
                      onChange={(date) => setStartDate(date)}
                      dateFormat="dd/MM/yyyy"
                      includeDateIntervals={[{ start: new Date(), end: addDays(new Date(), 30) }]}
                      className="cursor-pointer p-2"
                    />
                  </div>
                </div>
                <div>
                  <label
                    htmlFor="intrare"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Bilete Intrare Complex
                  </label>
                  <input
                    type="number"
                    name="intrare"
                    id="intrare"
                    placeholder="1"
                    value={state.intrare}
                    onChange={handleChange}
                    className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
                    required
                  />
                </div>
                <div>
                  <label
                    htmlFor="loParcare"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Numar Locuri de Parcare
                  </label>
                  <input
                    type="number"
                    name="locParcare"
                    id="locParcare"
                    placeholder="0"
                    min="0"
                    value={state.locParcare}
                    onChange={handleChange}
                    className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
                  />
                </div>

                <div>
                  <label
                    htmlFor="locPescuit"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Alege Locul de Pescuit
                  </label>

                  {
                    // Here I call that function inside a Suspense and things go south
                  }
                  <Suspense fallback="Cautam locurile de pescuit">
                    <PescuitSelect />
                  </Suspense>
                </div>

                <div>
                  <label
                    htmlFor="casuta"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Alege Casuta
                  </label>
                  <input
                    type="number"
                    name="casuta"
                    id="casuta"
                    placeholder="0"
                    min="0"
                    max="18"
                    value={state.casuta}
                    onChange={handleChange}
                    className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
                  />
                </div>
                <div>
                  <label
                    htmlFor="sezlong"
                    className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
                  >
                    Alege Sezlong
                  </label>
                  <input
                    type="number"
                    name="sezlong"
                    id="sezlong"
                    placeholder="0"
                    min="0"
                    max="21"
                    value={state.sezlong}
                    onChange={handleChange}
                    className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white"
                  />
                </div>
                <label
                  htmlFor="sedintaFoto"
                  className="relative inline-flex items-center mb-4 cursor-pointer"
                >
                  <input
                    type="checkbox"
                    name="sedintaFoto"
                    id="sedintaFoto"
                    className="sr-only peer"
                    checked={state.sedintaFoto}
                    onChange={handleChange}
                  />

                  <div className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
                  <span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
                    Sedinta foto
                  </span>
                </label>
                <label
                  htmlFor="petrecerePrivata"
                  className="relative inline-flex items-center mb-4 cursor-pointer"
                >
                  <input
                    type="checkbox"
                    name="petrecerePrivata"
                    id="petrecerePrivata"
                    className="sr-only peer"
                    checked={state.petrecerePrivata}
                    onChange={handleChange}
                  />

                  <div className="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
                  <span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">
                    Petrecere Privata
                  </span>
                </label>
              </>
            )}

            <button
              type="submit"
              className="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
            >
              Subimt
            </button>
          </form>
        </div>
      </div>
    </>
  )
}
export default Add

useCurrentBookings hook

import { useQuery } from "blitz"
import getAllBookings from "../queries/getAllBookings"
import format from "date-fns/format"

export const useCurrentBookings = (startDate) => {
  const [booking] = useQuery(getAllBookings, format(startDate, "yyyy-MM-dd")) // Here I query the database
  return booking
}

Actual call to the database

import db from "db"

//And this is the actual call to the database
export default async function getAllBookings(startsAt: string) {
  return await db.booking.findMany({
    where: { starts_at: { gte: new Date(startsAt) } },
  })
}


Solution 1:[1]

useEffect() runs everytime the dependencies change, inside the useEffect your updated the state and called useEffect again. Resulting in an infinite loop.

Resolution:

const [totalPrice, setTotalPrice] = useState(0);
useEffect(() => {
    const totalPrice =
      state.intrare * 20 +
      state.locParcare * 5 +
      (state.casuta ? 100 : 0) +
      (state.locPescuit ? 50 : 0) +
      (state.sedintaFoto ? 100 : 0) +
      state.sezlong * 15

    setTotalPrice(totalPrice);
  }, [state])

Solution 2:[2]

I had this problem before, keep refreshing a react component it's because of the Lifecycle in React. If you do not know about it, be sure to research it deeply.

https://www.w3schools.com/react/react_lifecycle.asp#:~:text=Each%20component%20in%20React%20has,Mounting%2C%20Updating%2C%20and%20Unmounting.

when you render your component it calls PescuitSelect() function

and in this function

setAvailableSpots({ ...availableSpots, pescuit: availableFishingSpots })

one of your state will be updated. in React when a state updated, the component will refresh again for showing new data of that state

Solution 3:[3]

Issue

The PescuitSelect component is unconditionally updating state, which is an unintentional side-effect and triggers a rerender.

const PescuitSelect = () => {
  const totalFishingSpots = Array.from(Array(114).keys())

  const bookings = useCurrentBookings(startDate) //useCurrentBookings is a hook I created

  const availableFishingSpots = totalFishingSpots.filter(
    (o1) => !bookings.some((o2) => o1 === o2.loc_pescuit)
  )
  console.log(availableFishingSpots)

  setAvailableSpots({ // <-- unconditional state update
    ...availableSpots,
    pescuit: availableFishingSpots
  })

  return (
    <select>
      {availableSpots.pescuit.map((value) => {
        return (
          <option value={value} key={value}>
            {value}
          </option>
        )
      })}
    </select>
  )
}

On top of this, PescuitSelect is redeclared each render cycle since it is defined inside another React component. It's an anti-pattern to declare React components within React components. They should all be declared at the top-level. Pass any callbacks in as props if necessary instead of trying to use values/callbacks closed over from the outer scope.

There is also a useEffect hook that is updating the state it's using as its dependency.

// Update the price as soon as any of the options changed
useEffect(() => {
  const totalPrice =
    state.intrare * 20 +
    state.locParcare * 5 +
    (state.casuta ? 100 : 0) +
    (state.locPescuit ? 50 : 0) +
    (state.sedintaFoto ? 100 : 0) +
    state.sezlong * 15

  setState({ ...state, totalPrice: totalPrice })
}, [state]);

Updating state.totalPrice updates the state value and also triggers a rerender which will cause the effect to run again and enqueue another state update. This totalPrice state is easily derived from the other existing state, and as such isn't necessary to also be stored in state.

Solution

Move the PescuitSelect component declaration outside this Add component.

Since it seems that PescuitSelect doesn't have any state and the logic to compute the available fishing spots only exists to update the state in the parent, this logic should be moved to the parent and an availableSpots array passed as a prop to PescuitSelect.

Example:

const PescuitSelect = ({ options }) => (
  <select>
    {options.map((value) => (
      <option value={value} key={value}>
        {value}
      </option>
    ))}
  </select>
);

The logic that was moved should be placed in an useEffect hook. Add any necessary dependencies.

Remove the useEffect hook that is only computing a totalPrice and just compute this each render. If computations like this are expensive then use the useMemo hook to memoize the result.

Example:

type booking = {
  starts_at: Date
  ends_at: Date
  intrare_complex: number
  loc_parcare: number
  loc_pescuit: number
  casuta: number
  sezlong: number
  sedinta_foto: boolean
  petrecere_privata: boolean
  total_price: number
}

const Add: BlitzPage = () => {
  //State for all options that will be added for the booking
  const [state, setState] = useState({
    intrare: 1,
    locParcare: 0,
    locPescuit: 0,
    casuta: 0,
    sezlong: 0,
    sedintaFoto: false,
    petrecerePrivata: false,
  });

  ...

  const [availableSpots, setAvailableSpots] = useState({
    pescuit: [0],
    casute: {},
    sezlonguri: {},
  });
  const bookings = useCurrentBookings(startDate);

  useEffect(() => {
    const availableFishingSpots = Array.from(Array(114).keys())
      .filter(o1 => !bookings.some((o2) => o1 === o2.loc_pescuit));

    console.log(availableFishingSpots);

    setAvailableSpots(availableSpots => ({
      ...availableSpots,
      pescuit: availableFishingSpots,
    }));
  }, [bookings]);

  ...

  // Update the price as soon as any of the options changed
  const totalPrice = useMemo(() => {
    return state.intrare * 20 +
      state.locParcare * 5 +
      (state.casuta ? 100 : 0) +
      (state.locPescuit ? 50 : 0) +
      (state.sedintaFoto ? 100 : 0) +
      state.sezlong * 15;
  }, [state]);

  ...

  return (
    <>
      ...
      <div className="mx-auto max-w-xs ">
        <div className="....">
          <form className="space-y-6" action="#" onSubmit={handleSubmit}>
            ...
            {state.petrecerePrivata ? (
              ...
            ) : (
              <>
                ...
                <div>
                  ...
                  <Suspense fallback="Cautam locurile de pescuit">
                    <PescuitSelect options={availableSpots.pescuit} />
                  </Suspense>
                </div>

                ...
              </>
            )}
            ...
          </form>
        </div>
      </div>
    </>
  )
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 alireza kargar
Solution 3