React checkbox doesn't keep toggled (think is in infinite loop) - reactjs

Basically i have this:
const [searchUser, setSearchUser] = useState<string[]>([])
Which i pass as a filter on an array:
reportsData
.filter((value: any) =>
searchUser.length > 0
? searchUser.includes(value.user.name)
: true
)
And i created checkboxes that passes values to this searchUser state so i can filter my array with one (or multiple checkboxes)
Like this:
const EmittersComponent: React.FC<PropsButton> = ({ label, onSelect }) => {
const [checked, setChecked] = useState(false)
function handleSelect() {
onSelect(label)
setChecked(!checked)
}
return (
<div className="grid grid-cols-3 gap-3 lg:grid-cols-2">
<li className="mt-4 flex items-start">
<div className="flex items-center h-5">
<input
type="checkbox"
onChange={() => {
setChecked(checked)
handleSelect()
}}
checked={checked}
className="h-4 w-4 focus:bg-indigo border-2 border-gray-300 rounded"
/>
</div>
<div className="ml-3 text-sm">
<span className="font-medium text-gray-700">
{label || 'Sem nome'}
</span>
</div>
</li>
</div>
)
}
function handleToggle(label: string) {
setSearchUser((prev) =>
prev.some((item) => item === label)
? prev.filter((item) => item !== label)
: [...prev, label]
)
}
const emittersComponent = () => (
<div>
{emittersData.map((value: any, index: any) => (
<EmittersComponent
key={index}
label={value.Attributes[2]?.Value}
onSelect={handleToggle}
/>
))}
</div>
)
Then i render it on my react component <ul>{emittersComponent()}</ul>
But the thing is, it is working everything correctly (if i select one or multiple checkboxes, it filters my array), but the checkbox won't keep toggled. It will render as if it was untoggled (the blank, unchecked box) no matter what i do.
I think is in an infinite loop and i can't fix it.

You have called setChecked in the
onChange={() => {
setChecked(checked)
handleSelect()
}}
and then setChecked is calling inside handleSelect function. That is not correct.
I assume it should be onChange={handleSelect}

You are creating a component inside another component with its own state, therefore a emittersComponent is created in every render.
Move Emitters component and emittersComponent(changed to EmittersComponentList ) function out of Quick Sight Component.
Try the below if this doesnt work then you have to have a logic to know which of the emittersData is checked.
const EmittersComponentList = ({ emittersData, handleToggle }) => (
<div>
{emittersData.map((value: any, index: any) => (
<EmittersComponent
key={value.Attributes[2]?.Value} // Dont add index as the key, add some unique val
label={value.Attributes[2]?.Value}
onSelect={handleToggle}
/>
))}
</div>
);
const EmittersComponent: React.FC<PropsButton> = ({ label, onSelect }) => {
const [checked, setChecked] = useState(false);
function handleSelect() {
onSelect(label);
setChecked(!checked);
}
return (
<div className="grid grid-cols-3 gap-3 lg:grid-cols-2">
<li className="mt-4 flex items-start">
<div className="flex items-center h-5">
<input
type="checkbox"
onChange={() => {
setChecked(checked);
handleSelect();
}}
checked={checked}
className="h-4 w-4 focus:bg-indigo border-2 border-gray-300 rounded"
/>
</div>
<div className="ml-3 text-sm">
<span className="font-medium text-gray-700">
{label || "Sem nome"}
</span>
</div>
</li>
</div>
);
}
;
In your Quick Sight Component
<div className="grid grid-cols-1 lg:grid-cols-2 lg:gap-6">
<div>
<span className="text-xl font-medium text-accent-9">
Escolha os emissores:
</span>
<ul>
{
/* Instead of emittersComponent()
use below
*/
<EmittersComponentList
emittersData={emittersData}
handleToggle={handleToggle}
/>
}
</ul>
</div>
<div>

Related

Component Rerendering On Change NextJS

I have a simple input with an onchange function that takes the value and sets the state for whichever one I want. I have used an input onChange before in other parts of the code, but this issue has never happened before. Every time I would type in a number in the input, it deselects the input and doesn't let me input anymore. This is the code for the input including the state set;=
const [calc, setCalc] = useState("");
const [iotInitial, setIotInitial] = useState(0);
const [iotCont, setIotCont] = useState(0);
const [iotGrowth, setIotGrowth] = useState(0);
const [iotSubmit, setIotSubmit] = useState(false)
const Calculator = () => {
if (calc === "1") {
return (
<div className="text-black p-2">
<h1 className="text-lg">Investment Over Time</h1>
<div className="">
<div className="flex flex-wrap gap-x-5">
<div className="flex flex-col">
<label>Initial Investment</label>
<input defaultValue={iotInitial} value="initial" type="number" className="rounded" onChange={(e) => setIotInitial(e.target.value)}/>
</div>
<div className="flex flex-col">
<label>Contributions (monthly)</label>
<input defaultValue={iotCont} value="cont" type="number" className="rounded" onChange={(e) => setIotCont(e.target.value)}/>
</div>
<div className="flex flex-col">
<label>Growth Time (years)</label>
<input defaultValue={iotGrowth} value="growth" type="number" className="rounded" onChange={(e) => setIotGrowth(e.target.value)}/>
</div>
<button className="bg-blue-300 hover:bg-blue-500 px-5 rounded" onClick={() => {setIotSubmit(true)}}>
Submit
</button>
</div>
{iotSubmit &&
<div>
{iotInitial}
{iotCont}
{iotGrowth}
</div>
}
</div>
</div>
);
} else if (calc === "2") {
return (
<div className="text-black p-2">
<h1 className="text-lg">Risk Analysis Using Average True Range</h1>
<p>Coming Soon</p>
</div>
);
} else if (calc === "3") {
return (
<div className="text-black">
<h1 className="text-lg">Hello</h1>
<p>{calc}</p>
</div>
);
}
};
This component keep rerendering and I don't know why. Any help would be useful.
you use value="initial" which is a string and what you should do is
...
<input
defaultValue="0"
value={iotCont}
type="number"
onChange={(e) => setIotCont(e.target.value)}
/>
...
The problem is that input uses value attribute as what it is gonna display
or you may simply remove value from your input to make it one way binding

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>
))}
</>
)

Passing values from a constant to the e.target.value

I am working on building out a radio component and when checked I want the values from the radio button to pass when set onBlur. I am trying to pass a function to the onBlur but I am not seeing the values on my Form Submit action.
const [isChecked, setIsChecked] = React.useState<boolean>(defaultValue);
const onValue = React.useMemo(() => {
if (!isChecked) return [`${label}, ${description}`];
}, [onChange]);
const checkedValue = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) =>
onChange?.(onValue, e.target.value),
[onChange]
);
return (
<div className="space-y-5">
<div className="relative flex items-start">
<div key={id} className="flex h-5 items-center">
<input
id={`${label}-id`}
name={label}
type="radio"
className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-500"
onBlur={checkedValue)}
onChange={() => setIsChecked(!isChecked)}
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor={`${label}-id`}
className="block font-medium text-gray-700"
>
{label}
</label>
<p id={`${label}-description`} className="text-gray-500">
{description}
</p>
</div>
</div>
</div>
);
};

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;

How to open one dropdown item?

friends, I have array of questions, and a dropdown list for them... i want to open any question, but all questions are opening together... please help
const FAQ = () => {
const [isOpenAnswer, setIsOpenAnswer] = useState(false)
const toggle = (id) => {
questions.forEach((q) => {
if(q.id === id){
setIsOpenAnswer((prevState) => !prevState)
}
})
}
return <Layout>
<div className="questionsBox pb-5">
<h2 className="title pt-4 pb-4" >Frequently Asked Questions</h2>
{questions.map((q, index) => {
return <div className="question pl-1 pt-3 pb-3 pr-1" key={index}>
<div className="d-flex justify-content-between">
<span className="questionTitle">{q.question}</span>
<img className="questionIcon"
src={Plus} alt="plus"
onClick={() => toggle(q.id)}
/>
</div>
{isOpenAnswer && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
</div>
})}
</div>
</Layout>
}
Use a Javascript object to track which unique q.id is being set to true.
const FAQ = () => {
const [isOpenAnswer, setIsOpenAnswer] = useState({})
const toggle = (id) => {
setIsOpenAnswer(prevState => ({
...prevState,
[id]: !prevState[id],
});
}
return <Layout>
<div className="questionsBox pb-5">
<h2 className="title pt-4 pb-4" >Frequently Asked Questions</h2>
{questions.map((q, index) => {
return <div className="question pl-1 pt-3 pb-3 pr-1" key={index}>
<div className="d-flex justify-content-between">
<span className="questionTitle">{q.question}</span>
<img className="questionIcon"
src={Plus} alt="plus"
onClick={() => toggle(q.id)}
/>
</div>
{isOpenAnswer[q.id] && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
</div>
})}
</div>
</Layout>
}
You're using the same prop for all of them here:
{isOpenAnswer && <p className="answer pt-2 pb-2">
{q.answer}
{q.source}
</p>}
Try saving something unique in state to identify what you're supposed to be showing, e.g.,
{selectedQuestionId && /* the rest */ }
and set the selectedQuestionId where you're currently setting isOpenAnswer .

Resources