Uncaught Error: Rendered more hooks than during the previous render - reactjs

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

Related

Why does my Layout of the carousel render vertically while flexed?

I have an carousel set up for view of store products.
Here you can test the code out here in the demo snippet Codesandbox
Mapping through my array of images it renders vertically on the screen when they should over lap each other given the view of a carousel to click from left to right. The container is relative to its position, flexed with 0 inset. I don't understand how the images render vertically
export default function Storetwo(){
const [currentSlide, setCurrentSlide] = useState(0);
const [currentProduct, setCurrentProduct] = useState(slides[0]);
const { handleSubmit, reset, setValue, control } = useForm({ defaultValues });
const [data, setData] = useState(null);
const onSubmit = data => console.log(data);
const handlePreviousClick = () => {
setCurrentSlide(currentSlide === 0 ? slides.length - 1 : currentSlide - 1);
setCurrentProduct(slides[currentSlide]);
}
const handleNextClick = () => {
setCurrentSlide(currentSlide === slides.length - 1 ? 0 : currentSlide + 1);
setCurrentProduct(slides[currentSlide]);
}
return(
<div className=' md:flex p-2 h-[100vh] '>
{/* image container */}
<div className=" w-[100%] md:w-[100%] ">
{slides.map((slide, index) => (
<div key={index} className={`relative flex inset-0 z-10 justify-center items-center ${index === currentSlide ? 'block' : 'hidden'}`}>
<Image src={slide.image} width={200} height={200} key={index} alt="" className="object-cover p-10 " />
<div className=" top-40 left-10 justify-center items-center mx-auto flex ">
<button className='text-3xl ' onClick={handlePreviousClick}><ArrowBackIosIcon/></button>
<button className='text-3xl ' onClick={handleNextClick}><ArrowForwardIosIcon /></button>
</div>
</div>
))}
</div>
.../
Here are the vertical rendering images
Your link is broken, which is one of the reasons why external live examples aren't recommended on StackOverflow. Use Snippets instead.
As for your question, I believe you're misunderstanding how flexbox works (or maybe it's just an oversight).
You're applying flex to the divs that are being rendered as a result of your mapping, when you should be adding it to their container instead. It's the container that will set its children position as flexbox. Read more here.
[...]
return(
<div className=' md:flex p-2 h-[100vh] '>
{/* image container */}
<div className="flex w-[100%] md:w-[100%] "> {/* <= here */}
{slides.map((slide, index) => (
<div key={index} className={`relative flex inset-0 z-10 justify-center items-center ${index === currentSlide ? 'block' : 'hidden'}`}>
<Image src={slide.image} width={200} height={200} key={index} alt="" className="object-cover p-10 " />
<div className=" top-40 left-10 justify-center items-center mx-auto flex ">
<button className='text-3xl ' onClick={handlePreviousClick}><ArrowBackIosIcon/></button>
<button className='text-3xl ' onClick={handleNextClick}><ArrowForwardIosIcon /></button>
</div>
</div>
))}
</div>

What ways are there to do an individual hover on elements that were iterated in React?

I'm rendering elements with the map function in react, the problem is that when hovering, the effect is applied to all the elements and what I'm looking for is an individual effect for each card.
This is my code:
const Card = () => {
const [isHovering, setIsHovering] = useState(false);
const handleMouseOver = () => {
setIsHovering(true);
};
const handleMouseOut = () => {
setIsHovering(false);
};
return (
<>
{productsList.map((product) => (
<div key={product.id}>
<div className="relative mb-4" onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
<img src={product.img} alt="product" className="w-fit h-fit jshover cursor-pointer" />
{isHovering ? <CardOptions /> : ""}
</div>
<div className="flex justify-center flex-col px-3">
<h3 className="captialize font-sans font-bold text-black mb-3">Adicolor Classics Joggers</h3>
<div className="flex justify-between ">
<span className="capitalize font-normal font-sans text-[#777777]">Dress</span>
<span className="font-sans font-bold">$63.85</span>
</div>
</div>
</div>
))}
</>
)
}
I am iterating an external array of objects with the information of each card.
As seen in the image, I hover my mouse over a card and the "shop now" box appears on all cards.
What would be the best way to do it?
Without iteration of course it worked, but then using React is pointless.
Edit: [SOLVED] The state has to have the index of the iteration of the function. So the conditional rendering has to be conditioned on the index and not on a boolean value.
Like this:
const Card = () => {
const [isHovering, setIsHovering] = useState(-1);
const handleMouseOver = (item) => {
setIsHovering(item);
};
const handleMouseOut = () => {
setIsHovering(-1);
};
return (
<>
{productsList.map((product, index) => (
<div key={product.id}>
<div className="relative mb-4" onMouseOver={() => {
handleMouseOver(index);
}} onMouseOut={handleMouseOut}>
<img src={product.img} alt="product" className="w-fit h-fit jshover cursor-pointer" />
{isHovering === index ? <CardOptions /> : ""}
</div>
<div className="flex justify-center flex-col px-3">
<h3 className="captialize font-sans font-bold text-black mb-3">{product.title}</h3>
<div className="flex justify-between ">
<span className="capitalize font-normal font-sans text-[#777777]">{product.category}</span>
<span className="font-sans font-bold">{product.price}</span>
</div>
</div>
</div>
))}
</>
)

React useEffect, useState & setTimeout combination

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

Store Image files in an array based on index in React js fails. Image always gets added to single array item only

I'm trying to create an image upload window where users can upload product images based for different product variations.
The intended output looks like:
I've to store the image file and url linked to the variationId and variationOptionId that the rendered component belongs to.
However, no matter which "Add Image" button i click, images get added only to the first component (blue, in the above example).
The imageVariations state looks like:
variationId: '',
variationName: '',
variationOptionId: '',
variationOptionName: '',
images: [
{
file: null,
url: '',
}
],
Code of Parent Component:
import React, { useEffect, useState } from 'react';
import ItemContainer from '../../common/ItemContainer';
import ProductImageUpload from '../imageStockPrice/ProductImageUpload';
const Tab3ProductImages = ({ product, loading, setLoading }) => {
const [imageVariations, setImageVariations] = useState([]);
// loop over product.productVariations and get the variation
// where variation.variesImage = true
useEffect(() => {
let tempVar = []; // has variationID, variationOptionID, and images array
product &&
product.productVariations.forEach((item) => {
if (item.variation.variesImage) {
tempVar.push({
variationId: item.variationId,
variationName: item.variation.name,
variationOptionId: item.variationOptionId,
variationOptionName: item.variationOption.name,
images: [],
});
}
});
setImageVariations(tempVar);
}, [product]);
// console.log('imageVariations', imageVariations);
const imageUploadHandler = () => {};
const imageDefaultHandler = () => {};
const imageDeleteHandler = () => {};
return (
<div className="py-4">
<div className="space-y-4">
{imageVariations &&
imageVariations.map((imageVar, index) => (
<div key={index}>
<ItemContainer
title={`${imageVar.variationName}: ${imageVar.variationOptionName}`}
>
<ProductImageUpload
loading={loading}
index={index}
imageVar={imageVar}
setImageVariations={setImageVariations}
imageUploadHandler={imageUploadHandler}
/>
</ItemContainer>
</div>
))}
</div>
</div>
);
};
export default Tab3ProductImages;
Code of the Child component - ProductImageUpload:
import React from 'react';
import { RiImageAddFill } from 'react-icons/ri';
import { MdOutlineCancel } from 'react-icons/md';
import LoadingButton from '../../formComponents/LoadingButton';
const ProductImageUpload = ({
loading,
index,
imageVar,
setImageVariations,
imageUploadHandler,
}) => {
// Handling Images
const handleImageChange = (e) => {
const tempArr = [];
[...e.target.files].forEach((file) => {
tempArr.push({
file: file,
url: URL.createObjectURL(file),
});
});
setImageVariations((prevState) => {
const newState = [...prevState];
newState[index].images = [...newState[index].images, ...tempArr];
return newState;
});
};
const removeImageFromList = (e) => {
setImageVariations((prevState) => {
const newState = [...prevState];
newState[index].images = newState[index].images.filter((item) => {
return item.url !== e.target.src;
});
return newState;
});
};
return (
<div className="space-y-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="my-auto">
<form onSubmit={imageUploadHandler}>
<div className="flex gap-4">
<label
htmlFor="categoryImages"
className="block w-full py-1 px-2 rounded cursor-pointer border
border-violet-700 bg-violet-50 hover:bg-violet-100 text-violet-700"
>
<span className="flex items-center gap-2">
<RiImageAddFill />
<span className="text-sm">Click to Add Images</span>
<span className="font-barlow text-sm">(max 10 images)</span>
</span>
<input
type="file"
id="categoryImages"
accept="image/*"
multiple
onChange={handleImageChange}
className="sr-only"
/>
</label>
{loading ? (
<LoadingButton />
) : (
<button className="text-sm py-1 px-4 rounded cursor-pointer border
border-blue-700 bg-blue-50 hover:bg-blue-100 text-blue-700">
Upload
</button>
)}
</div>
</form>
</div>
{/* upload progress bar */}
<div className="flex items-center">
<div className="bg-gray-200 w-full h-4 rounded-full overflow-hidden">
<div
className="h-4 bg-violet-500 text-xs font-medium text-center p-0.5
leading-none rounded-full transition-all duration-75"
style={{ width: `${progress}%` }}
>
<span
className={`ml-2 ${
progress === 0 ? 'text-gray-600' : 'text-blue-100'
}`}
>
{progress}%
</span>
</div>
</div>
</div>
{/* upload progress bar ends */}
{/* image preview section */}
<div>
<ul className="flex flex-wrap gap-2">
{imageVar &&
imageVar.images.length > 0 &&
imageVar.images.map((item, index) => (
<li key={index} className="relative">
<img
src={item.url}
alt="preview"
className="w-20 h-20 object-cover rounded shadow-lg border
hover:scale-110 transition duration-200"
/>
<button
onClick={removeImageFromList}
className="absolute -top-2 -right-2"
>
<MdOutlineCancel className="text-red-400 bg-white" />
</button>
</li>
))}
</ul>
</div>
{/* image preview section ends */}
</div>
);
};
export default ProductImageUpload;

button only clicks once, at one div element

this is the onclick function
export function changeColorButton() {
document.getElementById("friendDiv").style.background = "grey";
}
this is the output file. I want every button to be clickable and give background grey
{data.projects.map((project, key) => {
return (
<div id="friendDiv" className="relative">
<div key={key} className="flex flex-row items-center space-x-6 mb-6">
<img src={project.image} />
<div>
<h1 key={key} class=" text-xl font-bold">
{project.name}
</h1>
</div>
<button
className="absolute right-10 bg-bgButtonAddPeople p-2"
onClick={changeColorButton}
>
Legg til
</button>
</div>
</div>
);
});
}
You would just need to componentize the div element that is being mapped. So you can follow an approach like this.
Each FriendDiv element would have its own instance of changeColorButton, so it would apply the color to its own element.
FriendDiv.js
const FriendDiv = ({ key, project }) => {
const [isGrey, setIsGrey] = useState(false);
const changeColorButton = () => {
setIsGrey(!isGrey);
};
return (
<div
style={{ backgroundColor: isGrey ? 'grey' : 'default-color}}
id="friendDiv"
className="relative"
>
<div key={key} className="flex flex-row items-center space-x-6 mb-6">
<img src={project.image} />
<div>
<h1 key={key} class=" text-xl font-bold">
{project.name}
</h1>
</div>
<button
className="absolute right-10 bg-bgButtonAddPeople p-2"
onClick={changeColorButton}
>
Legg til
</button>
</div>
</div>
);
};
App.js
const App = () => {
return data.projects.map((project, key) => {
return <FriendDiv project={project} key={key} />;
});
};

Resources