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 querying the database 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) } },
})
}
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])
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
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>
</>
)
}
Related
I want to set the fetched data from firestore as initial value of useState but it gives me undefined value because I want to update user profile and I don't know the user edits or updates which property because I want to keep the other properties of the user the same, only change the edited one.
I've tried this code, but it gives me this error:
Uncaught (in promise) FirebaseError: Function updateDoc() called with invalid data. Unsupported field value: undefined (found in field surname in document users/DQjpLaKYVgVuH9TeqNomIEyuMJB2)
import React, { useState, useEffect } from 'react';
import { useAuthState } from 'react-firebase-hooks/auth';
import { doc, onSnapshot, updateDoc } from "firebase/firestore";
import { auth, db } from '../../firebase';
export default function Form({ setEditForm }) {
const [user, setUser] = useState([]);
const [currentUser] = useAuthState(auth);
// fetching user information from firestore
useEffect(() => {
const getUser = async () => {
const docRef = await doc(db, 'users', currentUser.uid)
try {
await onSnapshot(docRef, (doc) => {
setUser({
...doc.data(), id: doc.id
})
})
} catch (e) {
console.log(e)
}
}
getUser()
}, [])
const [name, setName] = useState(user.firstName);
const [surname, setSurname] = useState(user.surname);
const [biography, setBiography] = useState(user.biography);
const [location, setLocation] = useState(user.location);
// updating user's profile
const updateProfile = async (e) => {
e.preventDefault();
const docRef = doc(db, 'users', currentUser.uid);
await updateDoc(docRef, {
firstName: name,
surname: surname,
biography: biography,
location: location
})
}
console.log(user)
return (
<form
onSubmit={updateProfile}
className="flex flex-col w-4/6 lg:w-3/6"
>
<div className="lg:flex lg:flex-row lg:justify-between lg:gap-6">
<div className="lg:flex lg:flex-col lg:w-1/2">
<h2 className="text-left text-[#4699C2] font-bold py-2">Name: </h2>
<div className="border border-gray-300 rounded-md">
<input
type="text"
placeholder={name}
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full py-2 px-4 opacity-50 focus:opacity-100"
/>
</div>
</div>
<div className="lg:flex lg:flex-col lg:w-1/2">
<h2 className="text-left text-[#4699C2] font-bold py-2">Surname: </h2>
<div className="border border-gray-300 rounded-md">
<input
type="text"
placeholder={surname}
value={surname}
onChange={(e) => setSurname(e.target.value)}
className="opacity-50 px-4 focus:opacity-100 w-full py-2"
/>
</div>
</div>
</div>
<h2 className="text-left text-[#4699C2] font-bold py-2">Biograhpy: </h2>
<div className="border border-gray-300 rounded-md">
<textarea
onChange={(e) => setBiography(e.target.value)}
className="opacity-50 px-4 focus:opacity-100 w-full py-4"
>
{biography}
</textarea>
</div>
<h2 className="text-left text-[#4699C2] font-bold py-2">Location: </h2>
<div className="border border-gray-300 rounded-md">
<input
placeholder={location}
value={location}
onChange={(e) => setLocation(e.target.value)}
className="opacity-50 px-4 focus:opacity-100 w-full py-2"
/>
</div>
<div className="flex flex-row justify-center py-4">
<input
type="submit"
value="SAVE"
className="bg-[#4699C2] text-white fong-bold w-24 py-3 rounded-full mx-4 font-bold hover:bg-[#026FC2] hover:shadow-lg focus:bg-[#026FC2] focus:shadow-lg focus:outline-none focus:ring-0 active:bg-[#026FC2] active:shadow-lg transition duration-150 ease-in-out"
/>
<input
onClick={() => {
setEditForm(false);
}}
type="reset"
value="CANCEL"
className="bg-[#4699C2] cursor-pointer lg:bg-white hover:bg-[#026FC2] hover:text-white hover:shadow-lg focus:bg-[#026FC2] focus:shadow-lg focus:outline-none focus:ring-0 focus:text-white active:bg-[#026FC2] active:shadow-lg transition duration-150 ease-in-out text-white lg:text-[#4699C2] lg:border lg:border-[#4699C2] fong-bold w-24 py-3 rounded-full font-bold"
/>
</div>
</form>
);
}
Answering this as community wiki, As suggested by #yograjtandel, instead of storing the response in one single state, first declare all the states like name, biography, surname, etc... to null. then in useState set all the states ex. setName(doc.data().Name).
I want to replace a text input with a Combobox from https://headlessui.dev/react/combobox
Now the Combobox internal mechanism is working, but I'm not able to filter my results with the selected item.
This was the initial search input:
const SearchBar = ({ journeys, setSearchResults }) => {
const handleSubmit = (e) => e.preventDefault();
const handleSearchChange = (e) => {
if (!e.target.value) return setSearchResults(journeys);
const resultsArray = journeys.filter(
(journey) =>
// journey.arr.toLowerCase().includes(e.target.value) ||
journey.dep.toLowerCase().includes(e.target.value)
);
setSearchResults(resultsArray);
};
return (
<header>
<form className="p-4" onSubmit={handleSubmit}>
<input
className="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
type="text"
id="search"
onChange={handleSearchChange}
/>
</form>
</header>
);
};
export default SearchBar;
and here is the new Combobox:
import { useState } from 'react';
import { Combobox } from '#headlessui/react';
import { MapPinIcon } from '#heroicons/react/24/outline';
import places from '../data/places';
const DepartureBox = ({ journeys, setSearchResults }) => {
const handleSubmit = (e) => e.preventDefault();
const handleSearchChange = (e) => {
if (!e.target.value) return setSearchResults(journeys);
const resultsArray = journeys.filter(
(journey) =>
// journey.arr.toLowerCase().includes(e.target.value) ||
journey.dep.toLowerCase().includes(e.target.value)
);
setSearchResults(resultsArray);
};
const [query, setQuery] = useState('')
const filteredPlaces = query
? places.filter((place) => place.name.toLowerCase().includes(query.toLowerCase()))
: []
return (
// <header>
// <form className="p-4" onSubmit={handleSubmit}>
// <input
// className="block w-full max-w-lg rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
// type="text"
// id="search"
// onChange={handleSearchChange}
// />
// </form>
// </header>
<div className="m-4">
<Combobox
onChange={(place) => {
// TODO: onChange={handleSearchChange}
handleSearchChange
}}
as="div"
className="relative max-w-xl rounded-md ring-1 ring-black/5 divide-y divide-gray-100 overflow-hidden"
>
<div className='flex items-center px-2'>
<MapPinIcon className="h-6 w-6 text-gray-500" />
<Combobox.Input
onChange={(event) => {
setQuery(event.target.value)
}}
className="w-full h-10 bg-transparent border-0 focus:ring-0 text-sm text-gray-800 placeholder:text-gray-400"
placeholder='Select Departure Place...'
/>
</div>
{filteredPlaces.length > 0 && (
<Combobox.Options className="py-4 text-sm max-h-40 overflow-y-auto">
{filteredPlaces.map((place) => (
<Combobox.Option key={place.id} value={place}>
{({ active }) => (
<div className={`space-x-1 px-4 py-2 ${active ? 'bg-indigo-600' : 'bg-white'}`}>
<span className={`font-medium ${active ? 'text-white' : 'text-gray-900'}`}>{place.name}</span>
<span className={`font-medium ${active ? 'text-indigo-200' : 'text-gray-400'}`}>in {place.id}</span>
</div>
)}
</Combobox.Option>
))}
</Combobox.Options>
)}
{
query && filteredPlaces && filteredPlaces.length === 0 && (
<p className='p-4 text-sm text-gray-500'>No places found.</p>
)
}
</Combobox>
</div>
);
};
export default DepartureBox;
For sure I'm missing something at the onChange={(place) => level, but as this is my second react component, I'm lost at that point. Any help would be very appreciated.
Here is the codesandbox: https://codesandbox.io/p/github/sinyayadynya/roadbook/draft/codesandbox
Sorry if I ask again, I try to make dropdown from array in my database then use the dropdown to take the value I want looping the array to the dropdown in here but still not showed
//tipe akademik
//define state
const [postsTipe, setPostsTipe] = useState([]);
//useEffect hook
useEffect(() => {
//panggil method "fetchData"
fectDataTipe();
}, []);
//function "fetchData"
const fectDataTipe = async () => {
//fetching
const responseTipe = await axios.get('http://localhost:3000/api/tipe_akademik');
//get response data
const dataTipe = await responseTipe.data.data;
//assign response data to state "posts"
setPostsTipe(dataTipe);
}
const Dropdown = () => {
// dropdown props
const [dropdownPopoverShow, setDropdownPopoverShow] = React.useState(false);
const btnDropdownRef = React.createRef();
const popoverDropdownRef = React.createRef();
const openDropdownPopover = () => {
createPopper(btnDropdownRef.current, popoverDropdownRef.current, {
placement: "bottom-start",
});
setDropdownPopoverShow(true);
};
const closeDropdownPopover = () => {
setDropdownPopoverShow(false);
};
return (
<>
<a
className="text-blueGray-500 block"
href="#pablo"
ref={btnDropdownRef}
onClick={(e) => {
e.preventDefault();
dropdownPopoverShow ? closeDropdownPopover() : openDropdownPopover();
}} >
<div class="relative inline-block text-left">
<div>
<button type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500" id="menu-button" aria-expanded="true" aria-haspopup="true">
Tipe Akademik
</button>
</div>
</div>
</a>
<div
ref={popoverDropdownRef}
className={
(dropdownPopoverShow ? "block " : "hidden ") +
"bg-white text-base z-50 float-left py-2 list-none text-left rounded shadow-lg min-w-48"
}
>
{postsTipe.map((kategori)=> {
return(<>
<a
href="#pablo"
className={
"text-sm py-2 px-4 font-normal block w-full whitespace-nowrap bg-transparent text-blueGray-700"
}
onClick={(e) => e.preventDefault()}
>
{kategori.tipe_akademik}
</a> /</> )})}
</div>
</>
);
};
export default Dropdown;
Then I call it to my input form with import from dropdown and I want take the value in form and its still wrong. I dont know how to take the the value from import the dropdown named TipeDropdown
import TipeDropdown from "components/Dropdowns/Dropdown";
method "storePost"
const storePost = async (e) => {
e.preventDefault();
//send data to server
await axios.post('http://localhost:3000/api/akademik_a/store', {
tipeA: TipeA,
})
.then(() => {
//redirect
history.push('/admin/dafgur');
})
.catch((error) => {
//assign validation on state
setValidation(error.response.data);
})
};
export default function InputAkademik({ color }) {
return (
<>
<form onSubmit={ storePost }
<input className="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full ease-linear transition-all duration-150"
placeholder="" />
<div className="field mt-5">
<label className="label">Tipe Akademik</label>
<div className="controls">
<TipeDropdown value={TipeA} onChange={(e) => setTipe(e.target.value)}/>
<input type="text" className="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 bg-white rounded text-sm shadow focus:outline-none focus:ring w-full ease-linear transition-all duration-150" placeholder="" value={TipeInput} onChange={(e) => TipeInput(e.target.value)} />
</div>
</div> </div>
</>
);
};
To make the accordion component with Headless UI, I have used Disclosure component. But I have a problem to control the collapse/expand state for it's siblings.
So, I want to close other siblings when I open one, but Disclosure component is only supporting internal render props, open and close. So, I can't control it outside of the component and can't close others when I open one.
import { Disclosure } from '#headlessui/react'
import { ChevronUpIcon } from '#heroicons/react/solid'
export default function Example() {
return (
<div className="w-full px-4 pt-16">
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2">
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<span>What is your refund policy?</span>
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-5 w-5 text-purple-500`}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
If you're unhappy with your purchase for any reason, email us
within 90 days and we'll refund you in full, no questions asked.
</Disclosure.Panel>
</>
)}
</Disclosure>
<Disclosure as="div" className="mt-2">
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<span>Do you offer technical support?</span>
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-5 w-5 text-purple-500`}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
No.
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
</div>
)
}
How do we control the close/open state outside of the component?
I don't think so it's possible using HeadlessUI, although you can create your own Disclosure like component.
Lift the state up to the parent component by creating a disclosures state that stores all the information about the disclosures.
Loop over the disclosures using map and render them.
Render a button that toggles the isClose property of the disclosures and also handles the aria attributes.
On button click, toggle the isOpen value of the clicked disclosure and close all the other disclosures.
Checkout the snippet below:
import React, { useState } from "react";
import { ChevronUpIcon } from "#heroicons/react/solid";
export default function Example() {
const [disclosures, setDisclosures] = useState([
{
id: "disclosure-panel-1",
isOpen: false,
buttonText: "What is your refund policy?",
panelText:
"If you're unhappy with your purchase for any reason, email us within 90 days and we'll refund you in full, no questions asked."
},
{
id: "disclosure-panel-2",
isOpen: false,
buttonText: "Do you offer technical support?",
panelText: "No."
}
]);
const handleClick = (id) => {
setDisclosures(
disclosures.map((d) =>
d.id === id ? { ...d, isOpen: !d.isOpen } : { ...d, isOpen: false }
)
);
};
return (
<div className="w-full px-4 pt-16">
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2 space-y-2">
{disclosures.map(({ id, isOpen, buttonText, panelText }) => (
<React.Fragment key={id}>
<button
className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75"
onClick={() => handleClick(id)}
aria-expanded={isOpen}
{...(isOpen && { "aria-controls": id })}
>
{buttonText}
<ChevronUpIcon
className={`${
isOpen ? "rotate-180 transform" : ""
} h-5 w-5 text-purple-500`}
/>
</button>
{isOpen && (
<div className="px-4 pt-4 pb-2 text-sm text-gray-500">
{panelText}
</div>
)}
</React.Fragment>
))}
</div>
</div>
);
}
it is possible just you need to add some extra props selectors to the Disclosure.Button Component, in this case, I am adding aria-label='panel' like so...
import { Disclosure } from '#headlessui/react'
function MyDisclosure() {
return (
<Disclosure>
<Disclosure.Button aria-label="panel" className="py-2">
Is team pricing available?
</Disclosure.Button>
<Disclosure.Panel className="text-gray-500">
Yes! You can purchase a license that you can share with your entire
team.
</Disclosure.Panel>
</Disclosure>
)
}
next you need to select the following with "querySelectorAll" like...
<button
type='button'
onClick={() => {
const panels = [...document.querySelectorAll('[aria-expanded=true][aria-label=panel]')]
panels.map((panel) => panel.click())
}}
>
</button>
with this, you just need to change 'aria-expanded' to either 'true' or 'false' to expand or collapse
There's a way to do this with React (assuming you're using #headlessui/react) via useState:
const [disclosureState, setDisclosureState] = useState(0);
function handleDisclosureChange(state: number) {
if (state === disclosureState) {
setDisclosureState(0); // close all of them
} else {
setDisclosureState(state); // open the clicked disclosure
}
}
And in each Disclosure component, just pass an onClick callback to the Disclosure.Button:
<Disclosure.Button onClick={() => handleDisclosureChange(N)} />
Where N is the index of the clicked Disclosure (using 1 as the first Disclosure, since 0 handles all disclosures closed).
Finally, conditionally render the Disclosure.Panel based on the disclosureState:
{
disclosureState === N && (<Disclosure.Panel />)
}
Where N is the index of the clicked Disclosure. Using this method you can open just 1 disclosure at a time, and clicking an open disclosure will close all of them.
Hey Guys I'm pretty new to React and I'm running into a bit of a pickle here.
I'm making a simple CRUD website creator and I've got the add and delete function created and they work great! but I'm having trouble with the edit function.
I've based this on the this tutorial which works with String data-type
https://www.digitalocean.com/community/tutorials/react-crud-context-hooks
So in my mind this is how it should should work
I've got use state as passing an object with a couple of properties
const [selectedSection, setSelectedSection] = useState({
id: null,
section: {},
});
I've set the id to the current component I'm editing with
const currentSectionId = route.match.params.id;
in my useEffect I'm carrying over the current id with while setting the new compenent skipping the id in the current section
useEffect(() => {
const sectionId = currentSectionId;
const selectedSection = sections.find(
(currentSectionTraversal) => currentSectionTraversal.id === parseInt(sectionId)
);
setSelectedSection(selectedSection);},[currentSectionId, sections]);
const onSubmit = (e) => {
e.preventDefault();
editSection(selectedSection);
history.push("/");
console.log("selectedSection id",selectedSection.section, selectedSection.id)
};
and the button function to spread the selectedSection and change the only the requested value in the button.
const handleOnChange = (userKey, newValue) => setSelectedSection({ ...selectedSection, [userKey]: newValue });
in my render code I've got my button set up like
<button href="/" className="bg-green-400 w-mt hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
value={selectedSection.section}
onChange={() => handleOnChange("section", QuickLinks)}
type="submit"
>Add Section</button>
Now I've tried different things like changing the data-type in the useState Object, having the setSelectedSection in use effect to change the SelectedSection.section
but in the console what I'm noticing is the button is not carrying the data over.
I've imported my compenent as Quicklinks but I'm not sure why it's not passing the component into the selectedSection.section
here is the entire code of the editSection
import React, { useState, useContext, useEffect} from "react";
import { useHistory, Link } from "react-router-dom";
import { GlobalContext } from "./GlobalState";
import QuickLinks from '../components/sections/quick_links/quickLinks';
import QuickLinksPreview from './images/quick-link.jpg';
export const EditSection = (route) => {
let history = useHistory();
const { sections, editSection } = useContext(GlobalContext);
const [selectedSection, setSelectedSection] = useState({
id: null,
section: {},
});
const currentSectionId = route.match.params.id;
useEffect(() => {
const sectionId = currentSectionId;
const selectedSection = sections.find(
(currentSectionTraversal) => currentSectionTraversal.id === parseInt(sectionId)
);
setSelectedSection(selectedSection);
}, [currentSectionId, sections]);
const onSubmit = (e) => {
e.preventDefault();
editSection(selectedSection);
history.push("/");
console.log("selectedSection id",selectedSection.section, selectedSection.id)
};
const handleOnChange = (userKey, newValue) => setSelectedSection({ ...selectedSection, [userKey]: newValue });
if (!selectedSection || !selectedSection.id) {
return <div>Invalid Employee ID.</div>;
}
return (
<React.Fragment>
<div className="w-full container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<table>
<tbody>
{/* ----------------------------Item List Start COPY------------------------ */}
<tr>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-40 w-50 shadow">
<img className="h-40 w-full " src={QuickLinksPreview} alt="" />
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">Quick Links</div>
<div className="text-sm text-gray-500">Multi Card Quick Links<br></br>for Description and links</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-white-800"> Beta </span>
{/* Component Development Status
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-black-800"> Constrution </span>
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"> Active </span>
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-black-800"> Testing </span>
*/}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">S03-S5</td>
{/* Pricing Levels as Structure
S = Labels that it is sections
02 = is the Template Id for Developers
S = Standard Price
3 = Price level
*/}
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button href="/" className="bg-green-400 w-mt hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
value={selectedSection.section}
onChange={() => handleOnChange("section", QuickLinks)}
type="submit"
>Add Section</button>
</td>
</tr>
{console.log("Selected section", selectedSection.section)}
{/* ----------------------------Item List END COPY------------------------ */}
</tbody>
</table>
<div className="flex items-center justify-between">
<div className="block mt-5 bg-red-400 w-full hover:bg-red-500 text-white font-bold py-2 px-4 rounded focus:text-gray-600 focus:shadow-outline">
<Link to="/">Cancel</Link>
</div>
</div>
</form>
</div>
</React.Fragment>
);
};
Try using onClick instead of onChange for the button. I think onChange is for <input> tags not buttons. Also href is for links, not buttons. Unless this button is in a form type=submit isn't necessary. Also an arrow function isn't required for onClick. onChange={() => handleOnChange("section", QuickLinks)} -> onClick={handleOnChange("section", QuickLinks)}.