React useEffect, useState & setTimeout combination - reactjs

I'm trying to fetch 2 API's one by one. Second is dependent on the First API Data.
First API contains the movie data and the second API contains the Trailer Link Data for it.
const TrendingVideo = () => {
// Trending Data
const [content, setContent] = useState([])
// Trailer Data
const [trailerLink, setTrailerLink] = useState([])
// Pause / Play Function
const [isPlaying, setIsPlaying] = useState(true)
// Mute / Unmute Function
const [isMute, setIsMute] = useState(false)
// Fetching Trending Data (content)
const fetchTrending = async () => {
const { data } = await axios.get(`${TRENDING}${process.env.REACT_APP_API_KEY}`);
setContent(data.results);
}
// Fetching Trailer Data (trailerLink)
const getTrailer = async (media_type, id) => {
const { data } = await axios.get(`${baseURL}/${media_type}/${id}/videos?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`)
const filteredData = data.results.filter(search => search.type === "Trailer")
setTrailerLink(filteredData[0])
}
setTimeout(() => { setLoading(false) }, 1500)
useEffect(() => {
fetchTrending()
const timeout = setTimeout(() => { getTrailer(content[0]?.media_type, content[0]?.id) }, 1500)
return () => clearTimeout(timeout)
}, [content])
return (
<>
<ReactPlayer url={youtubeURL + trailerLink?.key} playing={isPlaying} muted={isMute} height="100%" width="100%" onEnded={() => setLoading(true)} />
<div className="flex gap-2">
<button onClick={() => setIsPlaying(!isPlaying)}> {isPlaying ? <BsFillPauseFill size={30} /> : <BsFillPlayFill size={30} />}</button>
<button onClick={() => setIsMute(!isMute)}>{isMute ? <FaVolumeMute size={30} /> : <FaVolumeUp size={30} />}</button>
</div>
</>
I'm getting the video Trailer data but it doesn't play until I manually click the Play button (twice). But when I switch to different routes and come back to the same, it plays automatically. Again when I reload the page it doesn't.
Full code
import { Dialog } from "#headlessui/react";
import axios from "axios";
import { useEffect } from "react";
import { useState } from "react";
import { BsFillPlayFill, BsFillPauseFill } from "react-icons/bs";
import ReactPlayer from "react-player";
import { baseURL, fullSizeImg, TRENDING, youtubeURL } from "../../../config/config";
import Button from "../../Sub/Button";
import { FaVolumeMute, FaVolumeUp } from "react-icons/fa"
import { useRef } from "react";
const TrendingVideo = () => {
// Trending Data
const [content, setContent] = useState([])
// Trailer Data
const [trailerLink, setTrailerLink] = useState([])
// Loading State
const [loading, setLoading] = useState(true)
// Pause / Play Function
const [isPlaying, setIsPlaying] = useState(null)
// Mute / Unmute Function
const [isMute, setIsMute] = useState(false)
// Fetching Trending Data (content)
const fetchTrending = async () => {
const { data } = await axios.get(`${TRENDING}${process.env.REACT_APP_API_KEY}`);
setContent(data.results);
}
// Fetching Trailer Data (trailerLink)
const getTrailer = async (media_type, id) => {
const { data } = await axios.get(`${baseURL}/${media_type}/${id}/videos?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`, { timeout: 5000 })
const filteredData = data.results.filter(search => search.type === "Trailer")
setTrailerLink(filteredData[0])
}
useEffect(() => {
fetchTrending()
const timeout = setTimeout(() => { getTrailer(content[0]?.media_type, content[0]?.id) }, 1500)
return () => clearTimeout(timeout)
}, [content])
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<div className="relative">
{loading ?
<div id="background" className="text-white bg-cover bg-top font-title" style={{ backgroundImage: `url(${fullSizeImg}${content[0]?.backdrop_path})` }} >
<div className="flex flex-col justify-center pt-20 h-[30vh] sm:h-[80vh] backdrop-blur-[1px] backdrop-brightness-[70%] bg-gradient-to-b from-transparent to-[#18181b]">
<div className="flex flex-col gap-5 px-16 sm:px-[10rem]">
<h1 className="text-2xl sm:text-[3rem] line font-bold text-blue-100 leading-tight">{content[0]?.title || content[0]?.name}</h1>
<p className="hidden sm:block text-xl truncate">{content[0]?.overview}</p>
<div className="flex gap-2 flex-wrap">
<button onClick={() => { setIsOpen(true); getTrailer(content[0]?.media_type, content[0]?.id) }} className="p-2 px-3 font-semibold rounded-md w-fit bg-yellow-500 shadow-md border h-12 duration-300 text-black hover:bg-yellow-600 border-yellow-500/70 flex items-center"><BsFillPlayFill size={25} /> Watch Trailer</button>
<Button media_type={content[0]?.media_type} id={content[0]?.id}>
<input type="submit" value="More Info" className="p-2 px-3 font-semibold rounded-md bg-black/60 shadow-md border duration-200 h-12 w-28 hover:border-2 hover:cursor-pointer text-yellow-500 hover:border-yellow-600 border-yellow-500/70" />
</Button>
</div>
</div>
</div>
</div>
:
<>
<div id="video" className="absolute aspect-video w-full top-[-2rem] z-0">
<ReactPlayer url={youtubeURL + trailerLink?.key} playing={isPlaying} muted={isMute} height="100%" width="100%" onEnded={() => setLoading(true)} />
</div>
<div className="flex flex-col justify-center pt-20 h-[30vh] sm:h-[80vh] backdrop-brightness-[100%] bg-gradient-to-b from-transparent to-[#18181b] text-white">
<div className="flex flex-col gap-5 px-16 sm:px-[10rem]">
<h1 className="text-2xl sm:text-[3rem] line font-bold text-blue-100 leading-tight">{content[0]?.title || content[0]?.name}</h1>
<p className="hidden sm:block text-xl truncate">{content[0]?.overview}</p>
<div className="flex justify-between flex-wrap">
<div className="flex gap-2 flex-wrap">
<button onClick={() => { setIsOpen(true); getTrailer(content[0]?.media_type, content[0]?.id); setIsPlaying(false) }} className="p-2 px-3 font-semibold rounded-md w-fit bg-yellow-500 shadow-md border h-12 duration-300 text-black hover:bg-yellow-600 border-yellow-500/70 flex items-center"><BsFillPlayFill size={25} /> Watch Trailer {trailerLink.toString()}</button>
<Button media_type={content[0]?.media_type} id={content[0]?.id}>
<input type="submit" value="More Info" className="p-2 px-3 font-semibold rounded-md bg-[#0d0d0d] shadow-md border duration-200 h-12 w-28 hover:border-2 hover:cursor-pointer text-yellow-500 hover:border-yellow-600 border-yellow-500/70" />
</Button>
</div>
<div className="flex gap-2">
<button className="p-2 rounded-full bg-[#0d0d0d] text-gray-400 shadow-md duration-300 hover:border-gray-500 border-2 border-gray-800 flex items-center" onClick={() => setIsPlaying(!isPlaying)}> {isPlaying ? <BsFillPauseFill size={30} /> : <BsFillPlayFill size={30} />}</button>
<button className="p-2 rounded-full bg-[#0d0d0d] text-gray-400 shadow-md duration-300 hover:border-gray-500 border-2 border-gray-800 flex items-center" onClick={() => setIsMute(!isMute)}>{isMute ? <FaVolumeMute size={30} /> : <FaVolumeUp size={30} />}</button>
</div>
</div>
</div>
</div>
</>
}
</div>
<Dialog
open={isOpen}
onClose={() => { setIsOpen(false) }}
className="relative z-50"
>
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="w-full max-w-4xl rounded bg-[#18181b]">
<div className="aspect-video">
<ReactPlayer url={youtubeURL + trailerLink.key} playing={true} controls={true} height="100%" width="100%" />
</div>
</Dialog.Panel>
</div>
</Dialog>
</div>
)
}
export default TrendingVideo

Related

Uncaught Error: Rendered more hooks than during the previous render

Hello I am developing a page to search for gifs and I get the error when I search for a larger amount of gifs than I asked for the first time, for example if I ask for 15 gisf and then another 15 different gifs I get no error but if I ask for 15 and then 30 the error appears.
The gif grid component:
`
function Gifs(props: any) {
return (
<div className="gifs-container grid grid-cols-1 md:grid-cols-4 gap-5 ">
{props.gifList.map((gif: gif, index: number) => {
const [active, setActive] = useState(false);
return (
//img container
<div
className="gif-box w-[200px] h-[150px] relative text-white"
key={index}
>
{/* Image */}
<img
src={gif.url}
alt={gif.title}
className="w-full h-full object-cover"
/>
{/* Image overlay */}
<div
className="img-overlay absolute w-full h-full opacity-0 top-0 flex flex-col
items-center bg-[rgba(0,0,0,0.6)] transition duration-100 hover:opacity-100
justify-around overflow-hidden"
>
{/* Overlay Tittle */}
<div className="overlay-tittle font-bold text-xl text-center w-[80%]">
{notGif(gif.title)}
</div>
{/* Overlay Buttons */}
<div className="overlay-buttons grid-cols-2 w-[40%] flex justify-between items-center">
<button
className="viewLink w-8 h-8 hover:opacity-60"
onClick={() => {
window.open(gif.url, "_blank");
}}
></button>
<div className={`copyMessage ${active ? "active" : ""}`}>
<button
className="copyLink w-9 h-9 hover:opacity-60"
onClick={() => {
navigator.clipboard.writeText(gif.url);
setActive(true);
setTimeout(() => {
setActive(false);
}, 500);
}}
></button>
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
export default Gifs;
the app component:
function App() {
const [gifList, setGifList] = useState<gif[]>([]);
const [gifCuantity, setGifCuantity] = useState("Cantidad");
const [gifName, setGifName] = useState("");
const setGifCuantityF = (newGifCuantity: string) => {
setGifCuantity(newGifCuantity);
};
const setGifNameF = (newGifName: string) => {
setGifName(newGifName);
};
const setGifListFun = (newGifList: gif[]) => {
setGifList(newGifList);
};
useEffect(() => {
if (gifCuantity !== "Cantidad" && gifName !== "") {
getGfifs(gifName, gifCuantity, setGifListFun);
setGifCuantity("Cantidad");
}
}, [gifName]);
return (
<div className="App">
<div className="h-screen ">
<div className="mx-auto flex flex-col justify-center items-center gap-8">
<Title />
<Browser
gifCuantity={gifCuantity}
setGifCuantityF={setGifCuantityF}
setGifNameF={setGifNameF}
/>
{/* <Cgifs gifList={gifList} /> */}
<Gifs gifList={gifList} />
</div>
</div>
</div>
);
}
`
i think the main proble is on the gifList useState, this is the useState that will contain all the gifs i fetch, so if the useState had array had 15 boxes and then i fetch for 20 gifs the error will say that the 5 new boxes were null before an now they are a useState

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 set location coordinates of that city when a button with city name is pressed in google places api in nextjs web application?

I'm building a NextJs web app where the user can explore various stores near him.
I'm using google places Api to select location from the places dropdown.
Based on the location the stores are displayed.
Rather typing the location I want a buttons with city name which on click will set the location of that city.
Here search bar and get current location button is present along with
city button
Location.tsx
useEffect(()=>{
if(!getLocation?.formattedAddress){
setLocation(true);
setHasLoction(false);
}else{
setHasLoction(true);
}
},[])
function changeLocation(data:any){
var location=JSON.stringify(data);
console.log(data?.formattedAddress);
document.getElementById("location_id").value=data?.formattedAddress;
setLocation(data?.formattedAddress);
if(location){
setHasLoction(true);
// closeLocation();
}
var {query ,pathname} = router;
var pathname="/"+router.locale+pathname
router.push(
{
pathname,
query: query,
},
{
pathname,
query: query,
},
);
handleLocation()
}
return (
<div style={{zIndex: 1000}} className={`absolute flex flex-col w-full z-1000 inset-0 shadow-lg transform ml-0 duration-200 ease-in
${location ? ' translate-y-0 ' : '-translate-y-full' } transform border-5 bg-gray-100 h-screen lg:h-100 xl:h-110 2xl:h-110 overflow-y-hidden overflow-hidden `}>
<div className='border-red-400 flex w-full'>
<div className='flex flex-col'>
<h4 className='block lg:hidden text-sm sm:text-2xl md:text-3xl lg:text-4xl mx-4 sm:mx-16 md:mx-16 mt-8 text-magenta font-heading font-semibold'>
</h4>
<div className=''>
<p className=' lg:hidden flex mx-4 sm:mx-16 md:mx-16 mt-4 font-semibold items-center text-xs
xs+:text-sm sm:text-sm text-gray-700'>
Local stores </p>
<p className=' lg:hidden flex mx-4 sm:mx-16 md:mx-16 mt-0 font-semibold items-center text-xs
xs+:text-sm sm:text-sm text-gray-700'>
</p>
</div>
</div>
<img src='/drop-down.jpg' className='hidden lg:block md:relative object-fill md:object-contain'/>
</div>
{/* <HeaderMiddle/> */}
<div className='flex items-center justify-between mx-auto mt-20 '>
{/* <Logo className="mx-auto" /> */}
<img src="/icons/x.svg" onClick = {closeLocation}
style={{zIndex: 100}} className={`${(hasLocation)?"":"hidden"} absolute font-bold z-40 h-7 w-7 top-2 bg-gold rounded-full right-2 2xl:top-5 text-gray-400 2xl:h-8 2xl:w-8 2xl:right-7 `}/>
{/* <h2 className=' font-md text-md sm:text-lg md:text-lg lg:text-lg 2xl:text-2xl '> Get best deals in your name </h2> */}
</div>
{/* <img src='/drop-down.jpg' className='relative top-0 object-contain'/> */}
<div id='location-input' style={{zIndex: 0}}
className='absolute flex flex-col justify-center
w-full lg:w-full items-center pt-36 sm:pt-20 md:pt-4 lg:pt-0 space-y-6 ml-0 mx-3
sm:mx-16 md:ml-16 lg:ml-6 xl:ml-8 2xl:ml-10 lg:mt-80'>
<div style = {{zIndex: 1000}}
className='w-full'>
<GooglePlacesAutocomplete onChange = {changeLocation}
address =
{getLocation?.formattedAddress} />
</div>
<div style={{zIndex: 1000}} className='w-full'>
<GetCurrentLocation onChange = {changeLocation} />
</div>
</div>
<div className='-mt-10'>
<button onClick={setCityLocation}
className='p-2 border'>
New York
</button>
</div>
{/* <Dialog.Overlay className="fixed inset-0 bg-gray-900 bg-
opaname-80 w-full h-full" /> */}
</div>
google-places-autocomplete.tsx
import { Autocomplete, useJsApiLoader } from "#react-google-maps/api";
import { Libraries } from "#react-google-maps/api/dist/utils/make-load-script-url";
import { LocationInput } from "#ts-types/generated";
import React, { useState ,useEffect } from "react";
import { useTranslation } from "next-i18next";
import Loader from "#components/ui/loader/loader";
import { useLocation } from "#contexts/location/location.context";
import { useCreateLogMutation } from "#data/log/use-create-log.mutation";
import useOnClickOutside from "#utils/use-click-outside";
const libraries: Libraries = ["places"];
// data,
// data: LocationInput;
export default function GooglePlacesAutocomplete({address,
onChange
}: {
onChange: any;
address :any
}) {
const { t } = useTranslation();
const [loc,setLocation]=useState(address);
const { mutate: createLog, isLoading: loading } = useCreateLogMutation();
const { isLoaded, loadError } = useJsApiLoader({
id: "google_map_autocomplete",
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAP_API_KEY!,
libraries,
});
const [autocomplete, setAutocomplete] = React.useState<any>(null);
const onLoad = React.useCallback(function callback(autocompleteInstance) {
setAutocomplete(autocompleteInstance);
}, []);
const onUnmount = React.useCallback(function callback() {
setAutocomplete(null);
}, []);
const onPlaceChanged = () => {
const place = autocomplete.getPlace();
if (!place.geometry || !place.geometry.location) {
console.log("Returned place contains no geometry");
return;
}
setLocation(place.formatted_address);
const location: any = {
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
formattedAddress: place.formatted_address,
};
for (const component of place.address_components) {
// #ts-ignore remove once typings fixed
const componentType = component.types[0];
switch (componentType) {
case "postal_code": {
location["zip"] = component.long_name;
break;
}
case "postal_code_suffix": {
location["zip"] = `${location?.zip}-${component.long_name}`;
break;
}
case "locality":
location["city"] = component.long_name;
break;
case "administrative_area_level_1": {
location["state"] = component.short_name;
break;
}
case "country":
location["country"] = component.long_name;
break;
}
}
if (onChange) {
onChange(location);
createLog({location:location.formattedAddress}, {
onSuccess: (data: any) => {
console.log(data)
},
onError: (error: any) => {
},
});
}
};
if (loadError) {
return <div>{t("common:text-map-cant-load")}</div>;
}
return isLoaded ? (
<Autocomplete
onLoad={onLoad}
onPlaceChanged={onPlaceChanged}
onUnmount={onUnmount}
fields={["address_components", "geometry.location", "formatted_address"]}
types={["address"]}
className="flex"
>
<input
type = "text"
defaultValue={loc}
style={{borderRadius:"5px"}}
className="p-3 pl-8 mx-8 w-full sm:px-8 sm:w-full sm:mx-auto xmd:mx-4 md:w-
full md:mx-auto
2xl:p-3 lg:p-3 lg:mx-auto lg:w-full 2xl:w-full 2xl:mx-auto font-
light
focus:border-accent focus:bg-light bg-gray-80
outline-none text-xs sm:text-sm md:text-base lg:text-lg
border-gray-300 border "
/>
</Autocomplete >
) : (
<div className="flex">
<Loader simple={true} className="w-6 h-6" />
</div>
);
}

TypeError: Cannot read property '_context' of undefined

I am creating a simple Todo List using React, Next.js, and TailwindCSS. For some reasons, I got this error: TypeError: Cannot read property '_context' of undefined.
This error occurs in TodoForm.js. I have defined showModal in index.js using TodoContext.Provider, but why does this error occur?
TodoContext.js
import { createContext } from "react";
const TodoContext = createContext(null);
export default TodoContext;
index.js
import { useState } from "react";
import Modal from "../components/Modal";
import TodoForm from "../components/TodoForm";
import TodoList from "../components/TodoList";
import TodoContext from "./TodoContext";
export default function Home() {
const [open, setOpen] = useState(true);
const [alertType, setAlertType] = useState("success");
const [alertMessage, setAlertMessage] = useState("");
const showModal = (type, msg) => {
setAlertType(type);
setAlertMessage(msg);
setOpen(true);
};
return (
<TodoContext.Provider value={{ showModal }}>
<div className="flex flex-col min-h-screen py-2 w-full items-center">
<TodoForm />
<Modal setOpen={setOpen} alertMessage={alertMessage} open={open} />
<TodoList />
</div>
</TodoContext.Provider>
);
}
TodoForm.js
import { addDoc, collection, serverTimestamp } from "#firebase/firestore";
import { useContext, useState } from "react";
import { db } from "../firebase";
const TodoForm = () => {
const [todo, setTodo] = useState({ title: "", detail: "" });
const { showModal } = useContext();
const onSubmit = async () => {
const collectionRef = collection(db, "todos");
const docRef = await addDoc(collectionRef, {
...todo,
timestamp: serverTimestamp(),
});
setTodo({ title: "", detail: "" });
showModal(
"bg-blue-200",
`Todo with id ${docRef.id} is added successfully! `
);
};
return (
<div className="flex w-3/6 flex-col justify-center mt-6">
<pre>{JSON.stringify(todo)}</pre>
<input
id="title"
type="text"
placeholder="Title"
value={todo.title}
onChange={(e) => setTodo({ ...todo, title: e.target.value })}
/>
<input
id="detail"
type="text"
placeholder="Detail"
value={todo.detail}
onChange={(e) => setTodo({ ...todo, detail: e.target.value })}
/>
<button
type="button"
onClick={onSubmit}
>
ADD A NEW TODO
</button>
</div>
);
};
export default TodoForm;
Modal.js
import { Fragment, useRef, useState } from "react";
import { Dialog, Transition } from "#headlessui/react";
import { ExclamationIcon } from "#heroicons/react/outline";
export default function Modal({ setOpenClick, alertMessage, open }) {
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="fixed z-10 inset-0 overflow-y-auto"
initialFocus={cancelButtonRef}
onClose={setOpenClick}
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
​
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-2">
<p className="text-sm text-gray-500">{alertMessage}</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setOpenClick(false)}
>
Deactivate
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setOpenClick(false)}
ref={cancelButtonRef}
>
Cancel
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
}
Would you let me know what I am missing here?
This is the image of showing error when using const showModal = useContext(TodoContext) on TodoForm.js
You are trying to destruct the context from the object
const { showModal } = useContext();
But the initial value is null
const TodoContext = createContext(null);
One solution is to give an initial value:
const TodoContext = createContext({showModal:()=>{}});
At Times This Issue Is As A Result Of Importing Something That Does Not Exist. Ensure That The Exported And Imported Names Are Correct.

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