How to set default selected option in combobox headless-ui? - reactjs

I want the first option to be selected by default in dropdown. I tried combobox defaultValue property but didn't work. How can i do this?
Combobox Component
import { useState } from 'react'
import { CheckIcon, ChevronUpDownIcon } from '#heroicons/react/20/solid'
import { Combobox } from '#headlessui/react'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function FormCombobox({
comboboxData,
label,
questionRange,
setQuestionRange,
}) {
const [query, setQuery] = useState('')
let items = comboboxData.map((item) => ({
id: item.content_object.nanoid,
name: item.content_object.name,
multiplier: item.multiplier,
}))
const filteredItems =
query === ''
? items
: items.filter((item) => {
return item.name.toLowerCase().includes(query.toLowerCase())
})
return (
<Combobox
as="div"
value={questionRange}
onChange={setQuestionRange}
className="my-5"
>
<Combobox.Label className="block text-left font-bold text-gray-700">
{label}
</Combobox.Label>
<div className="relative mt-1">
<Combobox.Input
className="w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"
onChange={(event) => setQuery(event.target.value)}
displayValue={(item) => item?.name}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</Combobox.Button>
{filteredItems.length > 0 && (
<Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{filteredItems.map((item) => (
<Combobox.Option
key={item.id}
value={item}
className={({ active }) =>
classNames(
'relative cursor-default select-none py-2 pl-3 pr-9',
active ? 'bg-indigo-600 text-white' : 'text-gray-900'
)
}
>
{({ active, selected }) => (
<>
<span
className={classNames(
'block truncate',
selected && 'font-semibold'
)}
>
{item.name}
</span>
{selected && (
<span
className={classNames(
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-indigo-600'
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
)}
</>
)}
</Combobox.Option>
))}
</Combobox.Options>
)}
</div>
</Combobox>
)
}

initialize the state of questionRange when declaring it .
const [questionRange, setQuestionRange] = useState(items[0])

Related

I'm using Tailwind in a React project. How do I successfully get the modal to fully go away after submitting? It seems that it's still greyed out

Here is my Signup.js
import React from "react";
import { useToken } from "./AuthenticateUser";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
export default function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [first_name, setFirst] = useState("");
const [last_name, setLast] = useState("");
const [zipcode, setZip] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const [, , , signup] = useToken();
const modal = document.getElementById("signup");
const handleFormSubmit = async (e) => {
e.preventDefault();
if (formValidation() === false) {
return;
}
signup(first_name, last_name, email, zipcode, password);
setEmail("");
setPassword("");
setFirst("");
setLast("");
setZip("");
navigate("/me");
setErrorMessage("");
modal.classList.remove("show");
};
function formValidation() {
let blankInputs = 0;
if (email.length === 0) {
blankInputs++;
}
if (password.length === 0) {
blankInputs++;
}
if (first_name.length === 0) {
blankInputs++;
}
if (last_name.length === 0) {
blankInputs++;
}
if (zipcode.length === 0) {
blankInputs++;
}
if (blankInputs === 5) {
setErrorMessage("Form submission is completely blank.");
return false;
}
if (blankInputs > 1) {
setErrorMessage("Form has multiple blank inputs.");
return false;
}
if (!validateEmail()) {
setErrorMessage("Whoops! Email format is invalid.");
return false;
}
if (zipcode.length < 5) {
setErrorMessage("Whoops! Zipcode needs to be at least 5 characters");
return false;
}
if (!password) {
setErrorMessage("Whoops! Password is required.");
return false;
}
if (!first_name) {
setErrorMessage("Whoops! First name is required.");
return false;
}
if (!last_name) {
setErrorMessage("Whoops! Last name is required.");
return false;
}
return true;
}
function validateEmail() {
const regex =
/^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regex.test(String(email).toLowerCase());
}
return (
<>
<div
className="modal fade fixed top-0 left-0 hidden w-full h-full outline-none overflow-x-hidden overflow-y-auto"
id="signup"
tabIndex="-1"
aria-labelledby="signupLabel"
aria-modal="true"
role="dialog"
>
<div className="modal-dialog modal-dialog-centered relative w-auto pointer-events-none">
<div className="modal-content border-none shadow-lg relative flex flex-col w-full pointer-events-auto bg-[#F0C797] bg-clip-padding rounded-md outline-none text-current">
<div className="modal-header p-6 mt-2 text-center">
<div className="flex justify-center items-center">
<h1 className="text-3xl font-bold mr-4">SIGN UP</h1>
<img src={require("../images/checklist.png")} width="50px" />
</div>
<svg
className="w-9 h-9 absolute top-3 right-2.5 text-black bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:bg-[#FEF5ED] hover:text-white ease-linear transition-all duration-150 cursor-pointer"
fillRule="currentColor"
data-bs-dismiss="modal"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
</div>
<div className="modal-body relative p-4">
<form
onSubmit={handleFormSubmit}
className="container max-w-sm mx-auto flex-1 flex flex-col items-center justify-center px-3"
>
<input
type="text"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="First Name"
onChange={(e) => setFirst(e.target.value)}
value={first_name}
/>
<input
type="text"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Last Name"
onChange={(e) => setLast(e.target.value)}
value={last_name}
/>
<input
type="email"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
value={email}
/>
<input
type="text"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Zipcode"
onChange={(e) => setZip(e.target.value)}
value={zipcode}
/>
<input
type="password"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
{errorMessage ? (
<div className="flex p-4 mb-4 text-sm text-red-700 border border-red-300 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800 items-center">
<img
src={require("../images/warning.png")}
width="30px"
style={{ marginRight: "15px" }}
/>
{errorMessage}
</div>
) : null}
<div className="flex flex-col items-center justify-end p-3 border-solid border-slate-200 rounded-b">
<button
// data-bs-dismiss="modal"
className="bg-black text-white font-bold uppercase text-sm px-6 py-3 rounded inline-flex group items-center justify-center cursor-pointer"
>
<span className="absolute w-0 h-0 transition-all duration-300 ease-out bg-[#F0C797] group-hover:w-32 group-hover:h-24 opacity-10"></span>
Order up!
</button>
</div>
</form>
<div className="flex items-center">
<a
data-bs-toggle="modal"
data-bs-target="#login"
className="mb-6 mt-4 mx-auto text-black-500 background-transparent font-bold underline uppercase text-sm focus:outline-none ease-linear transition-all duration-150 hover:text-white cursor-pointer"
>
Already have an account?
</a>
</div>
</div>
</div>
</div>
</div>
</>
);
}
**
Here is my Nav.js**
mport { useState } from "react";
function Nav() {
let [nav, setNav] = useState(false);
// nav = false
function handleNav() {
setNav(!nav);
}
return (
<nav className="flex justify-between items-center bg-[#FDECA9] py-3">
<div className="mx-auto mr-25">
<a href="/">
<div className="flex space-x-1 tracking-[4px] text-xl font-semibold items-center">
<span>PLATE</span>
<img
src={require("./images/plate.png")}
className="h-9"
alt="PlateMate Logo"
/>
<span>MATE</span>
</div>
</a>
</div>
<div className="hidden md:flex items-center">
<button
type="button"
className="bg-[#BB5855] mx-0 rounded text-[#FDECA9] text-sm py-1 px-4 relative inline-flex group items-center justify-center cursor-pointer"
data-bs-toggle="modal"
data-bs-target="#signup"
>
<span className="absolute w-0 h-0 transition-all duration-300 ease-out bg-white rounded-full group-hover:w-32 group-hover:h-32 opacity-10"></span>
SIGNUP
</button>
<button
type="button"
className="text-[#BB5855] mx-6 rounded text-sm outline outline-offset-4 outline-2 py-0 px-4 relative font-semibold text-center no-underline transition-all duration-300 ease-in-out cursor-pointer hover:text-[#bb58557c] "
data-bs-toggle="modal"
data-bs-target="#login"
>
LOGIN
</button>
</div>
<div className="block md:hidden">
{/* Mobile Hamburger Icon */}
<button
onClick={handleNav}
className="inline-flex items-center p-2 ml-3 text-sm md:hidden "
>
<img src={require("../src/images/hamburger.png")} width="30px" />
</button>
{/* Dropdown menu */}
<div
className={
nav
? "block absolute right-0 z-10 mt-0 w-56 mr-2 origin-top-right rounded-md bg-[#BB5855] shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
: "hidden"
}
>
<ul className="py-1 text-sm text-gray-100 divide-y ">
<div>
<a
data-bs-toggle="modal"
data-bs-target="#signup"
className="block px-4 py-2 transition-all duration-300 ease-in-out cursor-pointer hover:text-black"
>
SIGNUP
</a>
</div>
<div>
<a
data-bs-toggle="modal"
data-bs-target="#login"
className="block px-4 py-2 transition-all duration-300 ease-in-out cursor-pointer hover:text-black"
>
LOGIN
</a>
</div>
</ul>
</div>
</div>
</nav>
);
}
export default Nav;
I tried researching everything online but I can't find anything with Tailwind, only Bootstrap. I'm expecting the modal to not dismiss when there is an error message but dismiss completely after signed up. It does that but my issue is the grey background not disappearing. I have nothing in my CSS files that would tie to it. I've also tried commenting everything out on all of my files to see if anything was triggering it, however, it still appears. Please help!
Your code shows that you aren't really using React in the way it's intended. You should me managing the state in React, and not manipulating the DOM manually.
A common pattern for removing a modal is to use a state variable to handle the visibility of the component.
const [modalOpen, setModalOpen] = useState(false);
Then around your modal, you can render it conditionally:
return (<>
{modalOpen && (<YourModelHere />)}
</>)
You can then hide/show your modal using the setModalOpen() method.

why sanity is taking time to update the react application UI?

import { client, urlFor } from '../client';
import { Link, useNavigate } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
import { MdDownloadForOffline } from 'react-icons/md';
import { AiTwotoneDelete } from 'react-icons/ai';
import { BsFillArrowUpRightCircleFill } from 'react-icons/bs';
import { fetchUser } from '../utils/fetchUser';
const Pin = ({ pin: { postedBy, image, _id, destination, save } }) => {
// console.log(postedBy);
const [postHovered, setPostHovered] = useState(false);
const navigate = useNavigate();
const user = fetchUser();
const alreadySaved = !!save?.filter((item) => item.postedBy._id === user.sub)
?.length;
const savePin = (id) => {
if (!alreadySaved) {
client
.patch(id)
.setIfMissing({ save: [] })
.insert('after', 'save[-1]', [
{
_key: uuidv4(),
userId: user.sub,
postedBy: {
_type: 'postedBy',
_ref: user.sub,
},
},
])
.commit()
.then(() => {
window.location.reload();
});
}
};
const deletePin = (id) => {
client.delete(id).then(() => {
window.location.reload();
});
};
return (
<div className='m-2'>
<div
onMouseEnter={() => setPostHovered(true)}
onMouseLeave={() => setPostHovered(false)}
onClick={() => navigate(`/pin-detail/${_id}`)}
className='relative cursor-zoom-in w-auto hover:shadow-lg rounded-lg overflow-hidden transition-all duration-500 ease-in-out'
>
<img
className='rounded-lg w-full'
src={urlFor(image).width(700).url()}
alt='user-post'
/>
{postHovered && (
<div
className='absolute top-0 w-full h-full flex flex-col justify-between p-1 pr-2 pt-2 pb-2 z-50'
style={{ height: '100%' }}
>
<div className='flex items-center justify-between'>
<div className='flex gap-2'>
<a
href={`${image?.asset?.url}`}
download
onClick={(e) => e.stopPropagation()}
className='bg-white w-9 h-9 rounded-full flex items-center justify-center text-dark text-xl opacity-75 hover:shadow-md outline-none'
>
<MdDownloadForOffline />
</a>
{alreadySaved ? (
<button className='bg-red-500 opacity-70 hover:opacity-100 text-white font-bold px-5 py-1 text-base rounded-3xl hover:shadow-md outlined-none'>
{save?.length} Saved
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
savePin(_id);
}}
type='button'
className='bg-red-500 opacity-70 hover:opacity-100 text-white font-bold px-5 py-1 text-base rounded-3xl hover:shadow-md outlined-none'
>
Save
</button>
)}
</div>
<div className='flex justify-between items-center gap-2 w-full'>
{destination && (
<a
href={destination}
target='_blank'
rel='noreferrer'
className='bg-white flex items-center gap-2 text-black font-bold p-2 pl-4 pr-4 rounded-full opacity-70 hover:opacity-100 hover:shadow-md'
onClick={(e) => e.stopPropagation()}
>
<BsFillArrowUpRightCircleFill />
{destination.length > 15
? destination.slice(0, 15)
: destination}
</a>
)}
{postedBy?._id === user.sub && (
<button
onClick={(e) => {
e.stopPropagation();
deletePin(_id);
}}
className='bottom-0 bg-white w-9 h-9 rounded-full flex items-center justify-center text-dark text-xl opacity-75 hover:opacity-100 hover:shadow-md outline-none'
>
<AiTwotoneDelete />
</button>
)}
</div>
</div>
</div>
)}
</div>
<Link
to={`user-profile/${user?.sub}`}
className='flex gap-2 mt-2 items-center'
>
<img
className='w-8 h-8 rounded-full object-cover'
src={postedBy?.image}
alt='user-profile'
/>
<p className='font-semibold capitalize'>{postedBy.usernName}</p>
</Link>
{console.log(alreadySaved)}
</div>
);
};
export default Pin;
Here in the code, a user will save the pin (or post like snap) then button would be updated with saved button, but it is taking too much time to get updated , but when I save the pin sanity updates the pin's save array with the userId who have saved the pin (I can clearly see it on sanity studio in realtime).

How to return the complete list when the input is empty?

The search filter works practically as I wish, you enter a city and when you press the button it returns the results of the clinics that match the list of cities. The only problem is that I can only do a single search, then I have to reload the page, I need to delete the city from the input so that the complete list appears again and I can do another search, using typescript is complicating this part. To make it clearer, what I am trying to do is that whenever I enter a new city in the search bar and press the button, I get the result of the clinics in that city, now it just gives me the result only once, the next search gives me the result of clinic not found.
import React, { useState, useEffect } from 'react'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import contentUtils from '../../lib/contentUtils'
import Loader from '../spinner/Loader';
interface Props {
showModalLocator: boolean,
closeModalLocator: () => void
}
export const ClinicLocator: React.FC<Props> = ({ children, showModalLocator, closeModalLocator }) => {
const [clinicList, setClinicList] = useState<Clinic[] | undefined >([]);
const [text, setText] = useState("")
const textInput = () => {
text === "" ? clinicList : null
}
const fetchClinicList = async () => {
getClinic().then((response)=>{
console.log(response)
setClinicList(response)
}).catch ( (error) => {
console.error(error);
throw error;
});
}
const handleChange = () => {
const filterClinicList = clinicList && clinicList?.length > 0
? clinicList?.filter((clinic) => clinic?.province?.toLowerCase() === text.toLowerCase())
: undefined;
setClinicList(filterClinicList)
}
useEffect (() => {
fetchClinicList();
}, []);
return (
<>
<div>
{showModalLocator ? (
<>
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none">
<div className="relative p-2 w-full max-w-3xl h-full md:h-auto">
{/*content*/}
<div className="relative bg-white rounded-lg shadow">
{/*header*/}
<div className="flex justify-between items-start px-4 py-3 rounded-t border-b">
<h3 className="text-lg font-medium">Localizador de clinicas</h3>
<button className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" onClick={closeModalLocator}>
<svg aria-hidden="true" className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
{/*body*/}
<div className="relative px-3 py-3 flex-auto overflow-auto modal-body">
<h2 className="text-sm font-medium mb-2">¿Dónde te encuentras?</h2>
<input
value={text}
onChange= {(e) => {setText(e.target.value)
textInput}}
type="search"
className="w-100 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2"
placeholder="Introduce una ubicación"
/>
<div>
<h2 className="text-sm font-medium my-3">Resultados</h2>
<div className="w-100">
<iframe className="w-100" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2664.3238269926374!2d-0.3805919350162851!3d39.46959682083709!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0xd604f4bee0957f3%3A0x6686ff7d230b3965!2zQy4gZGUgU2FudC
BWaWNlbnQgTcOgcnRpciwgNjEsIHBpc28gMsK6LCBwdGEgMsKqLCA0NjAwMiBWYWzDqG5jaWEsIEVzcGHDsWE!5e0!3m2!1ses!2sus!4v1662388390673!5m2!1ses!2sus" loading="lazy"></iframe>
</div>
<div className="md:mt-4 overflow-auto relative py-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{clinicList && clinicList?.length === 0 && (
<div>Clinica no encontrada</div>
)}
{!clinicList ? <Loader /> :
clinicList.map((clinicFilter) => (
<div className="card bg-white px-2 py-3 h-36 md:h-32">
<button key={clinicFilter.id} type="button" className="text-left">
<div className="flex items-center gap-2 md:gap-4 md:gap-4">
<img className="h-24 w-2/5 min-w-40 object-cover object-center rounded-lg" src={contentUtils.getLargeImageUrl(clinicFilter.logo)} alt="#"/>
<div className="w-3/5">
<div className="text-md font-medium leading-5 clinic-title uppercase">{clinicFilter.title}</div>
<div className="flex items-center gap-2">
<div className="text-neutral-500 text-sm">{clinicFilter.propsPhone}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.mobile}</div>
</div>
<div className="text-teal-600 text-sm underline clinic-mail">{clinicFilter.email}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.registry}</div>
</div>
</div>
</button>
</div>
))
}
</div>
</div>
</div>
</div>
{/*footer*/}
<div className="flex items-center justify-end px-4 py-2 border-t border-solid border-slate-200 rounded-b gap-2">
<button className="btn text-black text-sm background-transparent px-8 outline-none focus:outline-none focus:ring-teal-600 focus:border-teal-600" type="button" onClick={closeModalLocator}>Cancelar</button>
<button className="btn bg-teal-600 hover:bg-teal-700 text-white text-sm active:bg-teal-700 px-8 outline-none focus:outline-none" type="button" onClick={handleChange} >Buscar</button>
</div>
</div>
</div>
</div>
<div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
</>
) : null}
</div>
</>
)
}
In your handleChange just return the List itself when text is null
const filterClinicList = clinicList && clinicList?.length > 0
? clinicList?.filter((clinic) => (!text || clinic?.province?.toLowerCase() === text.toLowerCase()))
: undefined; //!text evaluates to true when text is '' i.e. short circuit the filter
```
ideally what you do would be:
```
let filterClinicList = clinicList;
//only filter when you *need* to filter
if (text) {
filterClinicList = clinicList?.filter((clinic) => (!text || clinic?.province?.toLowerCase() === text.toLowerCase()));
}
```

Reset select value depending on siblings state in react-hook-form

I have two siblings and one is watching the state of another.
<Controller
control={control}
name="selectedBirthYear"
defaultValue={years[0]}
render={({ field }) => (
<SelectBirthYear
field={field}
years={years}
value={selectedYear}
defaultValue={selectedYear}
onChange={useEffect(() => {setSelectedYear(field.value)})}
/>
)}
/>
and
<Controller
control={control}
name="selectedBirthMonth"
defaultValue={months[0]}
render={({ field }) => (
<SelectBirthMonth
field={field}
startYear={startYear}
selectedYear={selectedYear}
months={months}
value={selectedMonth}
defaultValue={selectedMonth}
reducedMonths={reducedMonths}
onChange={useEffect(() => setSelectedMonth(field.value))}
/>
)}
/>
These two components are part of a Form parent. But, the issue is, that my useEffect, which watches year change, is not firing in combination with the Controller:
const watchYearChange = () => {
if(Number(selectedYear.name) == startYear){
setSelectedMonth(reducedMonths[reducedMonths.length - 1]);
}
};
useEffect(() => watchYearChange(), [selectedYear])
So, I was thinking to put this logic inside of the component itself and 'reset' the value of month on year change (Number(selectedYear.name) == startYear)
I've been experimenting, but for now with no success.
export const SelectBirthMonth = ({
startYear,
selectedYear,
months,
field,
selectedMonth,
setSelectedMonth,
reducedMonths}) => {
const { onChange, value } = field;
const watchYearChange = () => {
if(Number(selectedYear.name) == startYear){
setSelectedMonth(reducedMonths[reducedMonths.length - 1]);
}
console.log(reducedMonths[reducedMonths.length - 1]);
};
useEffect(() => watchYearChange(), [selectedYear])
return (
<Listbox value={Number(selectedYear.name) == startYear ? value == reducedMonths[reducedMonths.length - 1] : value} onChange={onChange}>
{({ open }) => (
<>
<Listbox.Label className="block text-sm font-medium text-gray-700">Geburtsmonat</Listbox.Label>
<div className="mt-1 relative">
<Listbox.Button className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-yellow-500 focus:border-yellow-500 sm:text-sm">
<span className="block truncate">{value?.name}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options
static
className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
>
{ Number(selectedYear.name) == startYear ? reducedMonths.map((month) => (
<ListboxOption
key={month.id}
value={month}
date={month.name}
>
</ListboxOption>
)) : months.map((month) => (
<ListboxOption
key={month.id}
value={month}
date={month.name}
>
</ListboxOption>
))
}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
)
}
Any ideas, how to 'reset' value, which is already coming from field of react hook form? I am really stuck.

React Stepper change Status onClick

I am new to Ract and building a multi step form in Next.js, where I also use Context. So my project structure is pretty wild and I don't get how / where to change the steps.status when moving to next step in the stepper. So far I have my context, managing half of states, basically the formData, but also the 'original' state of my stepper:
import { useState, createContext, useContext } from "react";
export const FormContext = createContext();
export default function FormProvider({ children }) {
const [data, setData] = useState({});
const [steps, setSteps] = useState([
{ name: 'Vertrag', status: 'current' },
{ name: 'Dateneingabe', status: 'upcoming' },
{ name: 'Bestätigung', status: 'upcoming' },
]);
const setFormValues = (values) => {
setData((prevValues) => ({
...prevValues,
...values,
}));
};
return (
<FormContext.Provider value={{ steps, data, setFormValues }}>
{children}
</FormContext.Provider>
);
}
export const useFormData = () => useContext(FormContext);
In Stepper.js I therefore import my formData:
import { CheckIcon } from '#heroicons/react/solid'
import { useContext } from 'react'
import { FormContext } from "../context";
// status: 'complete', 'current', 'upcoming'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Stepper() {
const formData = useContext(FormContext);
const steps = formData.steps
return (
<nav aria-label="Progress">
<div className="flex items-center flex-col">
<ol className="flex items-center sm:flex-col md:flex-row mx-auto mt-32 mb-8">
{steps.map((step, stepIdx) => (
<li key={step.name} className={classNames(stepIdx !== steps.length - 1 ? 'pr-16 sm:pr-32' : '', 'relative')}>
{step.status === 'complete' ? (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-yellow-500" />
</div>
<a
href="#"
className="relative w-8 h-8 flex items-center justify-center bg-yellow-500 rounded-full hover:bg-yellow-500"
>
<span className="h-9 flex flex-col items-center">
<span className="relative top-2 z-10 w-8 h-8 flex items-center justify-center rounded-full group-hover:bg-indigo-800">
<CheckIcon className="w-5 h-5 text-white" aria-hidden="true" />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600 mt-8">{step.name}</span>
</span>
</a>
</>
) : step.status === 'current' ? (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-gray-200" />
</div>
<a
href="#"
className="relative w-8 h-8 flex items-center justify-center bg-white border-2 border-yellow-500 rounded-full"
aria-current="step"
>
<span className="h-9 flex flex-col items-center">
<span className="z-10 w-8 h-8 flex items-center justify-center rounded-full group-hover:bg-indigo-800">
<span className="relative h-2.5 w-2.5 bg-yellow-500 rounded-full relative" style={{top: '0.8rem'}} />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600" style={{marginTop: '2.72rem'}}>{step.name}</span>
</span>
</a>
</>
) : (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-gray-200" />
</div>
<a
href="#"
className="group relative w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 rounded-full hover:border-gray-400"
>
<span className="h-9 flex flex-col items-center">
<span className="z-10 w-8 h-8 flex items-center justify-center rounded-full">
<span className="relative h-2.5 w-2.5 bg-transparent rounded-full group-hover:bg-gray-300" style={{top: '0.8rem'}} />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600" style={{marginTop: '2.72rem'}}>{step.name}</span>
</span>
</a>
</>
)}
</li>
))}
</ol>
</div>
</nav>
)
Moreover I have index.js page, where all the components come together
import { useState } from "react";
import Head from "next/head";
import Stepper from '../components/Stepper'
import styles from "../styles/styles.module.scss";
import FormCard from "../components/FormCard";
import Navbar from "../components/Navbar";
import { CheckIcon } from '#heroicons/react/solid'
import {
PersonalInfo,
ConfirmPurchase,
ContractInfo,
} from "../components/Forms";
import FormCompleted from "../components/FormCompleted";
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
const App = () => {
const [formStep, setFormStep] = useState(0);
const nextFormStep = () => setFormStep((currentStep) => currentStep + 1);
const prevFormStep = () => setFormStep((currentStep) => currentStep - 1);
const [activeStep, setActiveStep] = useState(0);
const handleNextStep = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handlePrevoiusStep = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
return (
<div>
<Head>
<title>Next.js Multi Step Form</title>
</Head>
< Navbar />
< Stepper activeStep={activeStep} />
<div className={styles.container}>
<FormCard currentStep={formStep} prevFormStep={prevFormStep}>
{formStep >= 0 && (
<ContractInfo formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep >= 1 && (
<PersonalInfo formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep >= 2 && (
<ConfirmPurchase formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep > 2 && <FormCompleted />}
</FormCard>
</div>
<div className="mt-1 mb-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
onClick={ () => { prevFormStep(); handlePrevoiusStep() }}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Back
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
onClick={ () => { nextFormStep(); handleNextStep() }}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Next
</a>
</div>
</div>
</div>
);
};
export default App;
As you see, Stepper is managed in three different files. But I am at least capable to change the activeStep index when clicking on Buttons, which already was a huge challenge for me. So now I also need the design to change. So that activeStep gets the step.status === 'current'. All stepps index < activeStep index should get step.status === 'complete' and logically all stepps index > activeStep index - step.status === 'upcoming'. Now I tried to handle this in index.js, but of course get back step is undefined, even though it is defined in Stepper.js through context.

Resources