How save style in the local storage - reactjs

I have such a project. Here I want the button border save in the local storage.The buttons are divided into categories. For example when you refresh the page after selecting a sports button, the border of the button disappears. I want save btn border in the localstorage. I saved the categories in memory, but I can't make the border of the selected button.How can I fix it?
import React, { useEffect, useState } from "react";
import SpinnerLoad from './components/SpinnerLoad'
import NewsItem from "./components/NewsItem";
import Category from "./components/data/Category"
const App = () => {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false)
const [selected, setSelected] = useState('');
const fetchValue = (category, index) => {
localStorage.setItem("category", category);
localStorage.setItem("selected", index);
fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`)
.then(res => res.json())
.then(res => {
setState(res.data)
setLoading(true)
})
.catch((error) => console.log(error))
setLoading(false);
};
const CategoryButton = ({ category, i }) => (
// passing index --> i to the fetch Value
<button onClick={() =>{ fetchValue(category,i) ; setSelected(i)} }
style={{border : selected === i ? '1px solid red' : null}} >{category}</button>
);
useEffect(() => {
let categoryValue = localStorage.getItem("category") || "all";
fetchValue(categoryValue)
const select = localStorage.getItem("selected") || "";
setSelected(select);
}, []);
return (
<>
<div className="header-bg">
<h1 className="mb-3">News</h1>
<div className="btns ">
{Category.map((value,i) => {
return <CategoryButton category={value} i={i}/>;
})}
</div>
</div>
<div className="news">
<div className="container">
<div className="row">
{
!loading
? <SpinnerLoad />
:
state.map((data, index) => {
return (
<NewsItem
imageUrl={data.imageUrl}
author={data.author}
title={data.title}
content={data.content}
date={data.date}
key={data.id}
/>
);
})
}
</div>
</div>
</div>
</>
);
};
export default App;

According to the code looks like you want to display data specific to a category set when the user clicks on the category buttons. and after the click, the correct data is rendered and the current category button receives a change in its style highlighting it is the current state.
I don't understand why you need to store anything in a client's localstorage,
I would not recommend storing too much in localStorage as it is limited and is used by different sites a user visits, I only store authentication tokens in localstorage and I believe that is the norm.
I've tried to create the effect you want without the need to store in local storage
import React, { useState, useCallback, useEffect } from "react";
import ReactDOM from "react-dom";
import { cat } from "../categories.js";
import { news } from "../news.js";
function Example() {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false);
const [selected, setSelected] = useState(null);
useEffect(() => {
function fetchFunction() {
setLoading(true);
for (let i = 0; i < news.length; i++) {
if (news[i].id === selected) {
const current = news[i].c;
setState(current);
}
}
setLoading(false);
}
fetchFunction();
}, [selected]);
return (
<>
<ol
style={{
width: "50%",
listStyle: "none",
display: "flex",
justifyContent: "space-between"
}}
>
{cat.map((item, index) => {
return (
<li key={index}>
<button
style={{ border: selected === item.id && "none" }}
onClick={() => {
setSelected(item.id);
}}
>
{item.name}
</button>
</li>
);
})}
</ol>
<section style={{ width: "100%", height: "70%" }}>
{state.map((item, index) => {
return (
<div
key={index}
style={{
width: "30%",
height: "30%",
background: "red",
display: "flex",
alignItems: "center",
justifyContent: "center",
margin: "1% 0 2% 0"
}}
>
{item.name}
</div>
);
})}
</section>
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);

You can save the selectedIndex in localStorage and retrieve it in the useEffect..
const CategoryButton = ({ category, i }) => (
// passing index --> i to the fetch Value
// setting selected as string instead of index for type checking
<button onClick={() =>{ fetchValue(category,i) ; setSelected(`${i}`)} }
style={{border : selected === `${i}` ? '1px solid red' : null}} >{category}</button>
);
const fetchValue = (category, index) => {
localStorage.setItem("category", category);
localStorage.setItem("selected", index);
// ...
}
useEffect(() => {
const select = localStorage.getItem("selected") || "";
// passing selectedIndex to the fetchValue, otherwise it becomes
//undefined..
fetchValue(categoryValue,select)
setSelected(select);
},[])

Related

useState([]) returns undefined instead of empty array using context

I am quite new to react and i´m tryng to make a wishlist using context. I have done something similar for my cart so I coppied all the code from there, but I cannot figure out why this is not working. When I call the array "wishlist" it returns undefined instead of an empty array despite having used useState([]) in the wishlist Context. (I DID THE SAME WITH THE CART AND WORKS PERFECTLY!) Here I leave the code:
wishlistContext.js
import { createContext, useContext, useState } from "react";
const WishlistContext = createContext([]);
export const useWishlistContext = () => useContext(WishlistContext);
export const WishlistProvider = ({ children }) => {
const [wishlist, setWishlist] = useState([]); // HERE I SET "wishlist" AS AN EMPTY ARRAY
const value = {
wishlist,
};
return (
<WishlistContext.Provider value={value} displayName="wishlistContext">
{children}
</WishlistContext.Provider>
);
};
Wishlist.jsx
import { useEffect, useState } from "react";
import { useWishlistContext } from "../context/wishlistContext";
export const Wishlist = () => {
const { wishlist } = useWishlistContext();
console.log(wishlist); // THIS IS WHAT RETURNS UNDEFINED
return (
<div className="w-75 m-auto my-5">
Something
</div>
);
};
NOW I LEAVE THE SAME EXAMPLE WITH CART THAT WORKS FLAWLESSLY
cartContext.js
import { createContext, useContext, useState } from "react";
const CartContext = createContext([]);
export const useCartContext = () => useContext(CartContext);
export const CartProvider = ({ children }) => {
const [cart, setCart] = useState([]); // HERE I SET "cart" AS AN EMPTY ARRAY
const removeProduct = (id) => {
const newCart = cart.filter((product) => product.id !== id);
setCart(newCart);
};
const addProduct = (item, qty) => {
const element = cart.find((product) => product.id === item.id);
if (!element) return setCart([...cart, { ...item, qty }]);
const newCart = cart.map((product) => {
if (product.id === item.id) {
return { ...product, qty: product.qty + qty };
}
return product;
});
setCart(newCart);
};
const getTotal = () =>
cart.reduce((acc, product) => acc + product.valor * product.qty, 0);
const getCartQty = () => cart.reduce((acc, product) => acc + product.qty, 0);
const emptyCart = () => setCart([]);
const value = {
cart,
addProduct,
removeProduct,
getCartQty,
getTotal,
emptyCart,
};
return (
<CartContext.Provider value={value} displayName="cartContext">
{children}
</CartContext.Provider>
);
};
Cart.jsx
import { BsFillCartFill } from "react-icons/bs";
import { useEffect, useState } from "react";
import { addOrder } from "../api/orders";
import { updateManyProducts } from "../api/products";
import { useCartContext } from "../context/cartContext";
import Swal from "sweetalert2";
export const Cart = () => {
const { getTotal, cart, emptyCart } = useCartContext();
console.log(cart);
if (cart.length <= 0)
return (
<div className="d-flex justify-content-evenly">
<div className="text-center m-auto" style={{ fontWeight: 600 }}>
<BsFillCartFill /> <br /> Su carrito esta vacío
</div>
</div>
);
const createOrder = async (e) => {
e.preventDefault();
const items = cart.map(({ id, nombre, qty, valor }) => ({
id,
nombre,
qty,
valor,
}));
let itemsAlert = "";
for (let i = 0; i < items.length; i++) {
itemsAlert +=
"<b>Item:</b> " +
items[i].nombre +
"<br><b>Cantidad:</b> " +
items[i].qty +
" <b>Valor Unidad:</b> $" +
items[i].valor +
"<br><br>";
console.log(itemsAlert);
}
const order = {
buyer: { name, phone, email },
items,
fecha: newdate,
estado: { orderState },
total: getTotal(),
};
const { buyer, fecha, total } = order;
const id = await addOrder(order);
await updateManyProducts(items);
emptyCart();
Swal.fire({
title: "Pedido Realizado",
html: `El id de su compra es <b>"${id}"</b> <br> Fecha: ${fecha}<br><br> <b>Detalle de compra:</b><br> <br> <b>Comprador:</b> ${buyer.name} <br><br> ${itemsAlert}<br> <b>Total: $${total}</b>`,
icon: "success",
confirmButtonText: "OK",
});
};
return (
<div className="w-75 m-auto my-5">
<h2 className="text-center my-5">
<BsFillCartFill /> Carrito <BsFillCartFill />{" "}
</h2>
{cart.map((product) => (
<div
key={product.id}
style={{
display: "flex",
gap: 50,
height: 100,
alignItems: "center",
width: "70%",
justifyContent: "space-evenly",
}}
className="m-auto"
>
<div>
Producto :{" "}
<b>
<b>{product.nombre}</b>
</b>
</div>
<div>
Valor unitario :{" "}
<b>
<b>${product.valor}</b>
</b>
</div>
<div>
Cantidad :{" "}
<b>
<b>{product.qty}</b>
</b>
</div>
</div>
))}
<span
style={{
marginBottom: 50,
textAlign: "center",
width: "70%",
fontSize: 20,
}}
className="mx-auto"
>
Total :{" "}
<b>
<b>${getTotal()}</b>
</b>
</span>
<form style={{ display: "grid", gap: 10 }} className="mb-5">
<span>Nombre</span>
<input
style={{ border: "1px solid black", height: 40 }}
onChange={(e) => setName(e.target.value)}
/>
<span>Telefono</span>
<input
type={"phone"}
style={{ border: "1px solid black", height: 40 }}
onBlur={(e) => validateInput(e.target.value, "phone")}
/>
<span>Email</span>
<input
type={"email"}
style={{ border: "1px solid black", marginBottom: 15, height: 40 }}
onBlur={(e) => validateInput(e.target.value, "email")}
/>
<span>Confirmar Email</span>
<input
style={{ border: "1px solid black", marginBottom: 15, height: 40 }}
onBlur={(e) => checkEmail(e.target.value)}
/>
<input type="submit" value="Enviar" onClick={createOrder} />
</form>
<div>
{mailError}
<br />
{phoneError}
</div>
</div>
);
};
I dont know if this has to do with some async property that i´m missing. Truth is I literally replicated the cart code, and this outcome really surprised me. Thank you all beforehand, this is giving me a big headache.
The JSX component must be wrapped by the context provider. Not sure if that is the issue without seeing the parent of your Wishlist component. Here is how I recreated the project and resolved issue:
Create React App 5.0.1
npx create-react-app test
cd test
npm start
Copy wishlistContext.js to src/context/
Copy wishlist.jsx to src/
To reproduce the issue, modify App.js to the following:
import './App.css';
import { Wishlist } from './wishList';
function App() {
return (
<Wishlist />
);
}
export default App;
See undefined in the console
Wrap the JSX element with the context provider
import './App.css';
import { Wishlist } from './wishList';
import { WishlistProvider } from './context/wishlistContext';
function App() {
return (
<WishlistProvider>
<Wishlist />
</WishlistProvider>
);
}
export default App;
See [] in the console

React gallery App. I want Add tags to an image individually but the tag is being added to all images. How can I solve this?

**> This is my Gallery Component **
import React, {useState} from 'react';
import useFirestore from '../hooks/useFirestore';
import { motion } from 'framer-motion';
const Gallery = ({ setSelectedImg }) => {
const { docs } = useFirestore('images');
here im setting the state as a Tags array
const [tags, setTags] = useState([""]);
const addTag = (e) => {
if (e.key === "Enter") {
if (e.target.value.length > 0) {
setTags([...tags, e.target.value]);
e.target.value = "";
}
}
};
functions for adding and removing Tags
const removeTag = (removedTag) => {
const newTags = tags.filter((tag) => tag !== removedTag);
setTags(newTags);
};
return (
<>
<div className="img-grid">
{docs && docs.map(doc => (
< motion.div className="img-wrap" key={doc.id}
layout
whileHover={{ opacity: 1 }}s
onClick={() => setSelectedImg(doc.url)}
>
here Im adding the Tag input to each Image...the problem is that when adding a Tag is added to all the pictures. I want to add the tags for the image that I´m selecting.
<div className="tag-container">
{tags.map((tag, ) => {
return (
<div key={doc.id} className="tag">
{tag} <span onClick={() => removeTag(tag)}>x</span>
</div>
);
})}
<input onKeyDown={addTag} />
</div>
<motion.img src={doc.url} alt="uploaded pic"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
>
</motion.img>
</motion.div>
))}
</div>
</>
)
}
export default Gallery;
The tags array that you are using to store values entered by the user are not unique with respect to each image item. Meaning, every image item in your program is using the same instance of the tags array, what you need to do is
Either create an object that stores an array of tags for each image:
const [tagsObj, setTagsObj] = {}, then while adding a new tag for say image_1, you can simply do setTagsObj(prevObj => {...prevObj, image_1: [...prevObj?.image_1, newTagValue]},
Or create an Image Component which would then handle tags for a single image:
Gallery Component:
{
imageList.map(imageEl =>
<ImageItem key={imageEl} image={imageEl} />
)
}
ImageItem Component:
import {useState} from 'react';
export default function ImageItem({image}) {
const [tags, setTags] = useState([]);
const addTag = (e) => {
if (e.key === "Enter") {
const newVal = e.target.value;
if (newVal.length > 0) {
setTags(prevTags => [...prevTags, newVal]);
e.target.value = '';
}
}
};
const removeTag = (removedTag) => {
setTags(prevTags => prevTags.filter((tag) => tag !== removedTag));
}
return (
<div style={{margin: '12px', padding: '12px', width: '100px', height:'100px', display:'flex', flexDirection: 'column', alignItems:'center'}}>
<span>{image}</span>
{tags.map((tag, index) => {
return (
<div key={tag+index}>
{tag} <span onClick={() => removeTag(tag)}>x</span>
</div>
);
})}
<input onKeyDown={addTag} />
</div>
);
}
Refer this sandbox for ease, if available Gallery unique image tags sandbox
I suggest using the second method, as it is easy to understand and debug later on.
I hope this helps, please accept the answer if it does!

How to use useState in loop?

I'm trying develop a little app in which on you can select multiple music album, using Next.js.
I display my albums like the image below, and I would like to add a check mark when clicked and hide it when clicked again.
My code looks like that :
import Image from "next/image";
import {Card,CardActionArea} from "#mui/material";
import { container, card } from "../styles/forms.module.css";
import album from "../public/album.json"
export default function Album() {
const albumList = {} ;
function addAlbum(albumId, image){
if ( !(albumId in albumList) ){
albumList[albumId] = true;
//display check on image
}
else{
delete albumList[albumId]
//hide check on image
}
console.log(albumList)
}
return (
<div className={container}>
{Object.keys(album.albums.items).map((image) => (
<Card className={card}>
<CardActionArea onClick={() => addAlbum(album.albums.items[image].id)}>
<Image alt={album.albums.items[image].artists[0].name} width="100%" height="100%" src={album.albums.items[image].images[1].url} />
</CardActionArea>
</Card>
))}
</div>
);
}
I know I should use useState to do so, but how can I use it for each one of my albums?
Sorry if it's a dumb question, I'm new with Hook stuff.
I think there are a few ways to go about this, but here is a way to explain the useState in a way that fits the question. CodeSandbox
For simplicity I made a Card component that knowns if it has been clicked or not and determines wither or not it should show the checkmark. Then if that component is clicked again a clickhandler from the parent is fired. This clickhandle moves the Card into a different state array to be handled.
The main Component:
export default function App() {
const [unselectedCards, setUnselectedCards] = useState([
"Car",
"Truck",
"Van",
"Scooter"
]);
const [selectedCards, setSelectedCards] = useState([]);
const addCard = (title) => {
const temp = unselectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setUnselectedCards(temp);
setSelectedCards([...selectedCards, title]);
};
const removeCard = (title) => {
console.log("title", title);
const temp = selectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setSelectedCards(temp);
setUnselectedCards([...unselectedCards, title]);
};
return (
<div className="App">
<h1>Current Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{unselectedCards.map((title) => (
<Card title={title} onClickHandler={addCard} key={title} />
))}
</div>
<h1>Selected Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{selectedCards.map((title) => (
<Card title={title} onClickHandler={removeCard} key={title} />
))}
</div>
</div>
);
}
The Card Component
export const Card = ({ onClickHandler, title }) => {
const [checked, setChecked] = useState(false);
const handleClickEvent = (onClickHandler, title, checked) => {
if (checked) {
onClickHandler(title);
} else {
setChecked(true);
}
};
return (
<div
style={{
width: "200px",
height: "250px",
background: "blue",
position: "relative"
}}
onClick={() => handleClickEvent(onClickHandler, title, checked)}
>
{checked ? (
<div
id="checkmark"
style={{ position: "absolute", left: "5px", top: "5px" }}
></div>
) : null}
<h3>{title}</h3>
</div>
);
};
I tried to make the useState actions as simple as possible with just a string array to help you see how it is used and then you can apply it to your own system.
You do not need to have a state for each album, you just need to set albumList as a state:
const [albumList, setAlbumList] = setState({});
function addAlbum(albumId, image) {
const newList = {...albumList};
if(!(albumId in albumList)) {
newList[albumId] = true;
} else {
delete albumList[albumId]
}
setAlbumList(newList);
}
And then in your loop you can make a condition to display the check mark or not by checking if the id is in albumList.

Update Child state from Parent using Context in React

I have a few buttons and "view all" button. The individual buttons load the coresponding data of that index or will show all the data by clicking the "view all" button. Problem I am running into is when I click my "view all" button in the parent it's not updating the state in the child component. On mounting it works as normal but on event handler in the "view all" it doesn't update. Any thoughts on where I am going wrong here?
JS:
...
const Context = createContext(false);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, styles, discription }) => {
const { activeViewAll, handleChange } = useContext(Context);
const [toggleThisButton, setToggleThisButton] = useState();
const handleClick = () => {
setToggleThisButton((prev) => !prev);
handleChange(discription, !toggleThisButton);
};
return (
<>
<Avatar
className={toggleThisButton && !activeViewAll ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(toggleThisButton)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(false);
useEffect(() => {
setActiveViewAll(true);
setSelected([...data]);
}, []);
const handleChange = (val, action) => {
let newVal = [];
if (activeViewAll) {
selected.splice(0, 3);
setActiveViewAll(false);
}
if (action) {
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log("action", action);
setSelected(newVal);
};
const handleViewAll = () => {
console.log("all clicked");
setActiveViewAll(true);
setSelected([...data]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</Context.Provider>
);
}
Codesanbox:
https://codesandbox.io/s/72166087-forked-jvn59i?file=/src/App.js:260-3117
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll state with the selected state.
Solution
When activeViewAll is true, pass the data array as the selected prop value to the ToggleContainer component, otherwise pass what is actually selected, the selected state.
Simplify the handlers. The handleViewAll callback only toggles the view all state to true, and the handleChange callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll then when toggling true clear the selected state array. Instead of duplicating the selected state in the children components, pass the selected array in the context and computed a derived isSelected state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}

Cannot display a number of input fields given a value

I'm trying to display fields based on the value of a props so let's say my props value = 2 then I want to display 2 inputs but I can't manage to get it work.
This is what I tried
const [numberOfFields, setNumberOfFields] = useState(0);
const [loadFields, setloadFields] = useState([]);
const addField = () => {
return loadFields.map((tier) => {
<div>
<p style={{color:'black'}}>Tier {tier + 1}</p>
<InputNumber />
</div>
})
}
const onPropsValueLoaded = (value) => {
let tmp = value
setNumberOfFields(tmp);
if (numberOfFields > 0) {
const generateArrays = Array.from(value).keys()
setloadFields(generateArrays);
} else {
setloadFields([]);
}
}
useEffect(() => {
onPropsValueLoaded(props.numberOfTiers);
}, [])
return (
<>
<Button type="primary" onClick={showModal}>
Buy tickets
</Button>
<Modal
title="Buy ticket"
visible={visible}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
>
<p style={{ color: 'black' }}>{props.numberOfTiers}</p>
{loadFields.length ? (
<div>{addField()}</div>
) : null}
<p style={{ color: 'black' }}>Total price: </p>
</Modal>
</>
);
so here props.NumberOfTiers = 2 so I want 2 input fields to be displayed but right now none are displayed even though loadFields.length is not null
I am displaying this inside a modal (even though I don't think it changes anything).
I am doing this when I load the page that's why I am using the useEffect(), because if I use a field and update this onChange it works nicely.
EDIT:
I changed the onPropsValueLoaded() function
const generateArrays = Array.from({length : tmp}, (v,k) => k)
instead of
const generateArrays = Array.from(value).keys()
There are couple of things you should fix in here,
First, you need to return div in addField function to render the inputs.
Second, you should move your function onPropsValueLoaded inside useEffect or use useCallback to prevent effect change on each render.
Third, your method of creating array using Array.from is not correct syntax which should be Array.from(Array(number).keys()).
So the working code should be , I also made a sample here
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [numberOfFields, setNumberOfFields] = useState(0);
const [loadFields, setloadFields] = useState([]);
const addField = () => {
return loadFields.map((tier) => {
return (
<div key={tier}>
<p style={{ color: "black" }}>Tier {tier + 1}</p>
<input type="text" />
</div>
);
});
};
useEffect(() => {
let tmp = 2; // tier number
setNumberOfFields(tmp);
if (numberOfFields > 0) {
const generateArrays = Array.from(Array(tmp).keys());
setloadFields(generateArrays);
} else {
setloadFields([]);
}
}, [numberOfFields]);
return (
<>
<button type="button">Buy tickets</button>
<p style={{ color: "black" }}>2</p>
{loadFields.length ? <div>{addField()}</div> : null}
<p style={{ color: "black" }}>Total price: </p>
</>
);
}

Resources