How to open one dropdown item? - reactjs

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 .

Related

how to get product id from sanity.io for every button that clicked in reactjs?

i'm trying to make product detail page but i am struggle to solve this problem. I fetch products from sanity.io and return the data using map. i want whenever i click the detail btn it's direct me to detail page and get the information of product
here is my code
const Products = () => {
const [allProducts, setAllProducts] = useState([]);
useEffect(() => {
async function getAllProducts() {
const allProduct = '*[_type == "product"]';
const response = await client.fetch(allProduct);
setAllProducts(await response);
}
getAllProducts();
}, []);
return (
<>
<Navbar />
<div className="container-fluid ">
<div className="row g-3 mb-5 ">
{allProducts.map((product) => {
console.log(product);
return (
<div className="col-12 col-md-3" key={product._id}>
<div className="card">
<img
src={urlFor(product.image[0])}
className="card-img-top"
alt={product.name}
/>
<div className="card-body">
<h1>${product.price}</h1>
<h5 className="card-title">{product.name} </h5>
<h6>{product.detail}</h6>
<div>
<Link to="/detail" className="btn btn-danger m-2">
Detail
</Link>
</div>
</div>
</div>
</div>
);
})}
</div>
<Footer />
</div>
</>
);
};

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

reactJS doesn't render the data after being fetched in useEffect hook

I have an API using axios which will fetch a list of products. I have called it inside useEffect() hook with [] as the second argument. I check the data before running into return command of the functional component and the data is there, but my ProductCard component are not rendered. Could you please help me with this?
FeaturedProducts.js
const FeaturedProducts = (props) => {
const [productList, setProductList] = useState([]);
// const productList = useSelector(productListSelector);
useEffect(async () => {
let rs = await fetchFeaturedProducts();
setProductList(rs);
}, []);
console.log(productList);
return (
<div className="container-fluid pt-5">
<div className="text-center mb-4">
<h2 className="section-title px-5"><span className="px-2">Featured</span></h2>
</div>
<div className="row px-xl-5 pb-3">
{
productList.map(product => {
<ProductCard id={product.id} name={product.name} price={product.price} img="abc"/> // need img={product.img}
})
}
</div>
</div>
);
}
export default FeaturedProducts;
fetchFeaturedProducts()
const fetchFeaturedProducts = async () => {
try {
let response = await AxiosClient.post("/product/search", {
searchType: "desc",
searchBy: "average_rating"
});
let data = response.data.data;
return data;
}
catch (error) {
raiseErrorMessages(error.response.data.errors);
return [];
}
}
ProductCard.js
const ProductCard = (props) => {
const detailUrl = "/product?id=" + toString(props.id);
return (
<div className="col-lg-3 col-md-6 col-sm-12 pb-1">
<div className="card product-item border-0 mb-4">
<div className="card-header product-img position-relative overflow-hidden bg-transparent border p-0">
<img className="img-fluid w-100" src={props.img} alt="" />
</div>
<div className="card-body border-left border-right text-center p-0 pt-4 pb-3">
<h6 className="text-truncate mb-3">{props.name}</h6>
<div className="d-flex justify-content-center">
<h6>{props.price}</h6>
{/* <h6 className="text-muted ml-2"><del>{props.price}</del></h6> */}
</div>
</div>
<div className="card-footer d-flex justify-content-between bg-light border">
<i className="fas fa-eye text-primary mr-1" />View Detail
<i className="fas fa-shopping-cart text-primary mr-1" />Add To Cart
</div>
</div>
</div>
)
}
export default ProductCard;
Thank you for your precious time.
that happens because you missed a return when you mapped your data
// .....
{
productList.map(product => {
return (<ProductCard id={product.id} name={product.name}
price={product.price} img="abc"/>) // need img=
{product.img}
})
}
or you can use ()
{
productList.map(product => (
<ProductCard id={product.id} name={product.name}
price={product.price} img="abc"/>) // need img=
{product.img})
}

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

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>

Irregular status 500 on Apollo GraphQL requests ("Variable X of required type \"String!\" was not provided.")

I get a network error just some of the times I call a lazyQuery in my React project with Apollo and GraphQL. In my app I can successfully get search results back containing various drinks. These are mapped to their own buttons. On clicking one of these buttons a function triggers that queries data about that specific drink.
Most of the time it works fine, but some of the times I get a status 500 error, saying that the cocktailName variable of the type string was not provided. When I've tried requesting the same drinks several times I've sometimes gotten a good response and sometimes not. When I try to debug the function that sends the query, it seems to receive the variable every single time, and I haven't managed to crash the app when I have debug breakpoints in that function. I'd really appreciate any assistance with this!
Main component for the drinks:
import React from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import { useQuery, useLazyQuery } from '#apollo/react-hooks';
import { RANDOM_COCKTAIL_QUERY, ONE_COCKTAIL_BY_NAME_QUERY } from './Queries';
import LoadingSpinner from './LoadingSpinner';
import CocktailSearchFieldAlt from './CocktailSearchFieldAlt';
import { cocktailIngredientList, cocktailMeasureList } from './ingredientLists';
// Imported ingredient and measure lists represent names of keys in objects
export default function Cocktails() {
const dataType = useStoreState((state) => state.cocktailDataType);
const changeDataType = useStoreActions((actions) => actions.changeCocktailDataType);
const dataSet = useStoreState((state) => state.cocktailDataSet);
const changeDataSet = useStoreActions((actions) => actions.changeCocktailDataSet);
const randomizeClick = () => {
if (dataType !== 'randomCocktail') {
changeDataType('randomCocktail');
changeDataSet('data');
}
refetch();
};
const specifikLookup = (drinkName) => {
if (dataType === 'randomCocktail') {
changeDataSet('specificData');
changeDataType('oneCocktailByName');
}
getDrink({ variables: { cocktailName: drinkName } });
};
const { loading, error, data, refetch } = useQuery(RANDOM_COCKTAIL_QUERY);
const [
getDrink,
{ data: specificData, loading: loadingSpecific, error: errorSpecific }
] = useLazyQuery(ONE_COCKTAIL_BY_NAME_QUERY);
if (loading || loadingSpecific) return <LoadingSpinner />;
if (error) return `Error! ${error}`;
if (errorSpecific) return `Error! ${errorSpecific}`;
return (
<section id="cocktail">
<h2 className="mainHeader mt-5 mb-4 scrollTrick">Cocktail</h2>
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-9">
<h4>{eval(dataSet)[dataType].strDrink}</h4>
<p>
{eval(dataSet)[dataType].strAlcoholic}
<br />
Best served in: {eval(dataSet)[dataType].strGlass}
</p>
<h6>Recipee</h6>
<div className="row">
<div className="col-md-4">
<ul>
{eval(dataSet) &&
cocktailIngredientList.map(
(x, i) =>
eval(dataSet)[dataType][x] && (
<li key={i}>{eval(dataSet)[dataType][x]}</li>
)
)}
</ul>
</div>
<div className="col-md-3">
<ul>
{data &&
cocktailMeasureList.map(
(x) => eval(dataSet)[dataType][x] && <li>{eval(dataSet)[dataType][x]}</li>
)}
</ul>
</div>
</div>
<h6>Instructions</h6>
<p>{eval(dataSet)[dataType].strInstructions}</p>
</div>
<div className="col-md-3">
<img src={eval(dataSet)[dataType].strDrinkThumb} alt="Cocktail" className="img-fluid mb-3" />
<button className="btn btn-primary btn-block" onClick={() => randomizeClick()}>
Randomize Cocktail
</button>
</div>
</div>
{/* <CocktailSearchField specificLookup={specifikLookup} /> */}
<CocktailSearchFieldAlt specificLookup={specifikLookup} />
</div>
</section>
);
}
Search component for the drinks:
import React, { useState } from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import { useLazyQuery } from '#apollo/react-hooks';
import { MULTIPLE_COCKTAILS_BY_NAME_QUERY, MULTIPLE_COCKTAILS_BY_INGREDIENT_QUERY } from './Queries';
import LoadingSpinner from './LoadingSpinner';
export default function SearchField(props) {
const [
searchInput,
setSearchInput
] = useState('');
const drinkOptionValue = useStoreState((state) => state.drinkOptionValue);
const changeDrinkOptionValue = useStoreActions((actions) => actions.changeDrinkOptionValue);
const handleSubmit = (e) => {
e.preventDefault();
getDrinks({ variables: { cocktailName: searchInput } });
getDrinksByIngredient({ variables: { ingredientName: searchInput } });
};
const [
getDrinks,
{ loading, data }
] = useLazyQuery(MULTIPLE_COCKTAILS_BY_NAME_QUERY);
const [
getDrinksByIngredient,
{ loading: loadingByIngredient, data: dataByIngredient }
] = useLazyQuery(MULTIPLE_COCKTAILS_BY_INGREDIENT_QUERY);
const lookupDrink = (e) => {
console.log(e.target.value);
changeDrinkOptionValue(e.target.value);
props.specificLookup(e.target.value);
};
if (loading || loadingByIngredient) return <LoadingSpinner />;
return (
<div>
<div className="row border-top mt-3 justify-content-center">
<div className="mt-3">
<form className="form-inline my-2 my-lg-0" onSubmit={handleSubmit}>
<input
className="form-control mr-sm-1"
type="text"
placeholder="Search cocktail"
onChange={(e) => setSearchInput(e.target.value)}
value={searchInput}
/>
<button className="btn btn-secondary my-2 my-sm-0 pl-3 pt-2 pb-2 pr-3" type="submit">
<i className="material-icons mt-2">search</i>
</button>
</form>
</div>
</div>
{data &&
(data.multipleCocktailsByName !== null ? (
<div className="container">
<div className="row">
<div className="col">
<h6 className="mt-3 mb-3 text-center">Search results: </h6>
</div>
</div>
<div className="row justify-content-center">
{dataByIngredient.multipleCocktailsByIngredient !== null &&
dataByIngredient.multipleCocktailsByIngredient.map((cocktailByIngredient) => {
if (
data.multipleCocktailsByName.some(
(cocktail) => cocktail.strDrink === cocktailByIngredient.strDrink
)
) {
return;
} else {
data.multipleCocktailsByName.push(cocktailByIngredient);
}
})}
{
(data.multipleCocktailsByName.sort(
(a, b) => (a.strDrink > b.strDrink ? 1 : b.strDrink > a.strDrink ? -1 : 0)
),
data.multipleCocktailsByName.map((cocktail, i) => (
<button
className="btn btn-outline-secondary p-1 menuButton btnAnimated mr-1 mb-1 border border-secondary"
key={i}
value={cocktail.strDrink}
onClick={lookupDrink}
>
<img src={cocktail.strDrinkThumb} className="menuPicture" alt="cocktail" />
{cocktail.strDrink}
</button>
)))
}
</div>
</div>
) : dataByIngredient.multipleCocktailsByIngredient !== null ? (
<div className="container">
<div className="row">
<div className="col">
<h6 className="mt-3 mb-3 text-center">Search results: </h6>
</div>
</div>
<div className="row justify-content-center">
{dataByIngredient.multipleCocktailsByIngredient.map((cocktail, i) => (
<button
className="btn btn-outline-secondary p-1 menuButton btnAnimated mr-1 mb-1 border border-secondary"
key={i}
value={cocktail.strDrink}
onClick={lookupDrink}
>
<img src={cocktail.strDrinkThumb} className="menuPicture" alt="cocktail" />
{cocktail.strDrink}
</button>
))}
</div>
</div>
) : (
<div className="row justify-content-center">
<p>No matching result</p>
</div>
))}
</div>
);
}

Resources