I am trying to create a button Add and Remove but i'm having issues that when I click one button and all the others was clicked as well.
Here is my following code:
function MyFruits() {
const fruitsArray = [
'banana',
'banana',
'orange',
'orange',
'strawberry',
'blackberry',
];
const cartArray : any[] = [];
let unique = [...new Set(fruitsArray)];
unique.sort((a, b) => {
return a.localeCompare(b);
})
const [addButton, setAddButton] = useState(true);
const [removeButton, setRemoveButton] = useState(false);
const [itemList, setItemList] = useState(fruitsArray)
const [cart, setCart] = useState<typeof cartArray>([])
const addClick = (fruit: any, index: any) => {
setItemList([...itemList]);
setCart([...cart, fruit]);
setAddButton(false);
setRemoveButton(true);
}
const removeClick = (fruit: any, index: any) => {
let removeItem = [...cart];
removeItem = removeItem.filter((cartItem) => cartItem !== fruit);
setCart(removeItem);
setAddButton(true);
setRemoveButton(false);
}
return (
<div className="App">
<header className="App-header">
<ul>
{unique.map((fruit, index) => (
<div>
<li key={index}>{fruit}</li>
{itemList.length !== 1 && addButton && (
<button onClick={() => addClick(fruit, index)}>Add</button>
)}
{removeButton && (
<button onClick={() => removeClick(fruit, index)}>Remove</button>
)}
</div>
))}
</ul>
<div className="cart">
Cart: {cart.map(cartItem => (
<p>{cartItem}</p>
))}
</div>
</header>
</div>
);
}
export default MyFruits;
How can I improve my click function for Add and Remove ?
You've to track the addButton and removeButton state changes for each and every fruit you've in your array. So better you create state objects such as addButtonFruits and removeButtonFruits to track them.
I've created a JavaScript implementation similar to your code and try to use it as a supportive material for your implementation.
import React, { useEffect, useState } from "react";
function App() {
const fruitsArray = [
"banana",
"banana",
"orange",
"orange",
"strawberry",
"blackberry",
];
const cartArray = [];
let unique = [...new Set(fruitsArray)];
unique.sort((a, b) => {
return a.localeCompare(b);
});
const [addButtonFruits, setAddButtonFruits] = useState({});
const [removeButtonFruits, setRemoveButtonFruits] = useState({});
const [itemList, setItemList] = useState(fruitsArray);
const [cart, setCart] = useState([]);
/*
instead of having this useEffect, you can directly set the default state using useState as follows:
const [addButtonFruits, setAddButtonFruits] = useState(
{
banana: true,
orange: true,
strawberry: true,
});
*/
useEffect(() => {
const initialAddButtonFruits = {};
fruitsArray.forEach((each) => (initialAddButtonFruits[each] = true));
console.log("initial add-fruit-buttons state: ", initialAddButtonFruits);
setAddButtonFruits(initialAddButtonFruits);
}, []); // only executed for initial rendering
const addClick = (fruit, index) => {
setItemList([...itemList]);
setCart([...cart, fruit]);
const addButtonFruitsTemp = addButtonFruits;
addButtonFruitsTemp[fruit] = false;
setAddButtonFruits(addButtonFruitsTemp);
const removeButtonFruitsTemp = removeButtonFruits;
removeButtonFruitsTemp[fruit] = true;
setRemoveButtonFruits(removeButtonFruitsTemp);
};
const removeClick = (fruit, index) => {
let removeItem = [...cart];
removeItem = removeItem.filter((cartItem) => cartItem !== fruit);
setCart(removeItem);
const addButtonFruitsTemp = addButtonFruits;
addButtonFruitsTemp[fruit] = true;
setAddButtonFruits(addButtonFruitsTemp);
const removeButtonFruitsTemp = removeButtonFruits;
removeButtonFruitsTemp[fruit] = false;
setRemoveButtonFruits(removeButtonFruitsTemp);
};
console.log("addButtonFruits: ", addButtonFruits);
console.log("removeButtonFruits: ", removeButtonFruits);
return (
<div className="App">
<header className="App-header">
<ul>
{unique.map((fruit, index) => (
<div>
<li key={index}>{fruit}</li>
{itemList.length !== 1 && addButtonFruits[fruit] && (
<button onClick={() => addClick(fruit, index)}>Add</button>
)}
{removeButtonFruits[fruit] && (
<button onClick={() => removeClick(fruit, index)}>
Remove
</button>
)}
</div>
))}
</ul>
<div className="cart">
Cart:{" "}
{cart.map((cartItem) => (
<p>{cartItem}</p>
))}
</div>
</header>
</div>
);
}
export default App;
Following is the view
Hope this will solve your issue.
Related
Please help me! Delete Icon is not functional, when I click on delete icon it delete all the contact, on refreshing, it returns all the previous contacts. I am also using localStorage.
I have added all the Component of the React App Project.
App.js
import { v4 as uuid } from "uuid";
const App = () => {
const LOCAL_STORAGE_KEY = "contacts";
const [contacts, setContacts] = useState([]);
const addContactHandler = (contact) => {
console.log(contact);
setContacts([...contacts, { id: uuid(), ...contact }]);
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
};
useEffect(() => {
const retrieveContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
);
if (retrieveContacts) {
setContacts(retrieveContacts);
}
}, []);
useEffect(() => {
if (contacts.length) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}
}, [contacts]);
return (
<>
<div className="app">
<Header />
<AddContact addContactHandler={addContactHandler} />
<ContactList contacts={contacts} getContactId={removeContactHandler} />
</div>
</>
);
};
export default App;
ContactList.js
const ContactList = (props) => {
const deleteContactHandler = (id) => {
props.getContactId(id);
};
const renderContactList = props.contacts.map((contact) => {
return (
<>
<ContactCard
contact={contact}
clickHandler={deleteContactHandler}
key={contact.id}
/>
</>
);
});
return (
<>
<div className="contactList">
<h2 className="contactList__title">Contact List</h2>
<div className="contactList__container">
{renderContactList}
</div>
</div>
</>
);
};
ContactCard.js
const ContactCard = (props) => {
const { id, name, email } = props.contact;
return (
<>
<div className="contactCard">
<div className="contactCard__contact">
<img
className="contactCard__userIcon"
src={userIcon}
alt="user-icon"
/>
<div className="contactCard__userName">
<h2>{name}</h2>
<p>{email}</p>
</div>
</div>
<div className="contactCard__delIcon">
<img
src={delIcon}
alt="del-icon"
onClick={() => props.clickHandler(id)}
/>
</div>
</div>
</>
);
};
export default ContactCard;
I have researched out the references. Unable to get the Solution.
The effect to store the contacts do not save empty arrays.
Thats why you get the old array after refreshing your page.
Just remove the condition.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
But you should consider to remove this effect.
Save the contacts directly after setting the state instead.
const addContactHandler = (contact) => {
console.log(contact);
const newContactList = [...contacts, { id: uuid(), ...contact }];
setContacts(newContactList);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newContactList));
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newContactList));
};
I'm trying to splice an array hook State, so I can remove items from my list. I know you're not supposed to modify any hook states directly, so I made a copy into array2 before settting it, but it's removing all items below the one I click, instead of just the one I click. Any ideas?
export default function Shopping() {
const [item, setItem] = useState('Default');
const [list, setList] = useState([]);
const [array, setArray] = useState([]);
let array2 = [];
const name = ({ target }) => setItem(target.value);
const remove = ({ target }) => {
for (let x = 0; x <= array.length; x++) {
if (array[x] === target.value) {
array2 = list;
array2 = array2.splice(x, 1);
}
setList([...list, array2]);
}
};
const create = () => {
if (item !== 'Default' && document.getElementById('box').value !== '') {
let value = document.getElementById('box').value;
setArray([...array, value]);
setList([
...list,
<div>
<p>
{' '}
{item} Qty: 1<button>+</button>
<button>-</button>
<button
value={document.getElementById('box').value}
onClick={remove}
>
x
</button>
</p>
</div>,
]);
} else {
alert('Please enter an Item');
}
document.getElementById('box').value = '';
setItem('Default');
};
const clear = () => setList([]);
return (
<div>
<input type='textbox' id='box' onChange={name} />
<input type='button' value='Add' onClick={create} />
<input type='button' value='Clear' onClick={clear} />
{list}
</div>
);
}
You should just store the strings in the list array and display the elements using a map function:
export default function Shopping() {
const [item, setItem] = useState('Default');
const [list, setList] = useState([]);
const name = ({ target }) => setItem(target.value);
const remove = (index) => {
const filteredList = [...list].splice(index, 1);
setList(filteredList);
};
const create = () => {
if (item !== 'Default' && item !== '') {
setList([...list, item]);
} else {
alert('Please enter an Item');
}
setItem('Default');
};
const clear = () => setList([]);
return (
<div>
<input type='textbox' id='box' onChange={name} />
<input type='button' value='Add' onClick={() => create()} />
<input type='button' value='Clear' onClick={() => clear()} />
{list.map((it, index) => {
return (
<div key={index}>
<p>
{' '}
{it} Qty: 1<button>+</button>
<button>-</button>
<button value={it} onClick={() => remove(index)}>
x
</button>
</p>
</div>
);
})}
</div>
);
}
```
Just Update the code in remove function because array2 = list also copies the reference of list array so when you update the array2 it also update the list array.so replace the remove function code with given code below .
if ((array[x])===target.value){
array2 = list.map(item=>item)
array2 = array2.splice(x,1)
}
I have one button in front each list item when I click on the watched button, I want the text of the button to be changed to not watched(or when click on not watched it change to watched) and to be included in the watched list. At the top, I have three buttons, watched and not watched , which one I clicked on. Show me the list of the movies that I changed, their state and for third button(with text of all )it shows the whole list.I think that my problem is handleWatchedBtn function . this is picture of project maybe it is simple my explanation! thank you for your help.
import React, { useEffect, useState } from "react";
const App = () => {
const [Movies, setMovie] = useState([]);
const [Loading, setLoading] = useState(true);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
setMovie(
response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}))
);
setLoading(false);
});
}, []);
function handleWatchedBtn(id) {
setMovie(() =>
Movies.map((movie) => {
if (movie.id === id) {
return { movie, watched: !movie.watched };
}
return movie;
})
);
}
function handleWatchedChange(filter) {
setfilterByWatch({ filterByWatch: filter });
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
function filterItems() {
return Movies.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input type="text" value={Keyword} onChange={handleKeywordChange} />
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filterItems().map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
You could introduce a new array state which stores all filtered movies and is updated every time the movies or the filter are updated.
You can then pass its reference to the map function that generates the list.
Also, notice that I've added the spread operator (...) in the handleWatchedBtn function and adjusted the handleWatchedChange method to update the state to a string and not an object.
Try to change your code like this:
import React, { useEffect, useState } from "react";
const App = () => {
const [Movies, setMovie] = useState([]);
const [filteredMovies, setFilteredMovies] = useState([]);
const [Loading, setLoading] = useState(true);
const [Keyword, setKeyword] = useState("");
const [OverSeven, setOverSeven] = useState(false);
const [filterByWatch, setfilterByWatch] = useState("ALL");
useEffect(() => {
fetch("http://my-json-server.typicode.com/bemaxima/fake-api/movies")
.then((response) => response.json())
.then((response) => {
const newMovies = response.map((item) => ({
id: item.id,
name: item.name,
rate: item.rate,
watched: false,
}));
setMovie(newMovies);
setLoading(false);
});
}, []);
useEffect(() => {
// Update filtered movies when the data or the filter changes
const newFilteredMovies = Movies.filter((item) =>
item.name.toLowerCase().includes(Keyword.toLowerCase())
)
.filter((item) => (OverSeven ? item.rate > 7 : true))
.filter((item) =>
filterByWatch === "ALL"
? true
: item.watched === (filterByWatch === "WATCHED")
);
setFilteredMovies(newFilteredMovies);
}, [Movies, filterByWatch]);
function handleWatchedBtn(id) {
setMovie(() =>
Movies.map((movie) => {
if (movie.id === id) {
// Add the spread operator here
return { ...movie, watched: !movie.watched };
}
return movie;
})
);
}
function handleWatchedChange(filter) {
// Change this line
setfilterByWatch(filter);
}
function handleKeywordChange(e) {
setKeyword(e.target.value);
}
function handleOverSevenChange(e) {
setOverSeven(e.target.checked);
}
if (Loading) {
return "Please wait...";
}
return (
<div>
<div>
<div>
Keyword
<input type="text" value={Keyword} onChange={handleKeywordChange} />
</div>
<div>
<button onClick={() => handleWatchedChange("ALL")}>all</button>
<button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
<button onClick={() => handleWatchedChange("NOT_WATCHED")}>
not watch
</button>
</div>
<div>
Only over 7.0
<input
type="checkbox"
checked={OverSeven}
onChange={handleOverSevenChange}
/>
</div>
<div>
<ul>
{filteredMovies.map((movie) => (
<li data-id={movie.id}>
{`${movie.name} ${movie.rate}`}{" "}
<button onClick={() => handleWatchedBtn(movie.id)}>
{movie.watched ? "Watched" : " Not watched"}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
);
};
export default App;
This is my component of imageslider with button next and previous
I need som help how i can get individual img_src values and add them into another array and them use them in my image slider.
I welcome every solution corresponding to my aproach
const ImageSlider = () => {
const dispatch = useDispatch();
const ImageList = useSelector((state) => state.ImageList);
const { loading, error, Images } = ImageList;
useEffect(() => {
dispatch(ListImages());
}, [dispatch]);
var items = [Images.photos];
console.log(Images);
const classes = useStyles();
function Item(props) {
return (
<Paper>
{props.item.map(data => (
<img src={data.img_src} />
))}
{({ onClick, className, style, next, prev }) => {
return (
<Button onClick={onClick} className={classes.button} style={style}>
{next && "Next"}
{prev && "Previous"}
</Button>
);
}}
</Paper>
);
}
return (
<>
{loading ? (
<Loader />
) : error ? (
<h1>{error}</h1>
) : (
<Carousel>
{items.map((item, i) => (
<Item key={i} item={item} />
))}
</Carousel>
)}
</>
);
};
export default ImageSlider;
```
First of all you should move the Item component out of the ImageSlider. It is being redefined every render. You can use localState to keep track of the index.
const useImageIndexer = (maxIndex) => {
const [index, setIndex] = useState(0);
const nextImage = () => {
setIndex((current) => Math.min(maxIndex, current + 1));
};
const prevImage = () => {
setIndex((current) => Math.max(0, current - 1));
};
return [index, nextImage, prevImage];
}
Then to use inside the slider
const ImageSlider = () => {
const dispatch = useDispatch();
const ImageList = useSelector((state) => state.ImageList);
const photos = ImageList.Images.photos;
const [index, nextImage, prevImage] = useImageIndexer(photos.length);
const currentPhoto = photos[index];
// Further down in the code
if(loading) {
return (<Loader />);
}
if (error) {
return (<div>Oh no!</div>);
}
return (<div>
<img src={img.src} />
<button onClick={prevImage}>Previous</button>
<button onClick={nextImage}>Next</button>
</div>);
It seemed like you were wrapping photos which sounds like an array inside another array, that doesn't look right.
var items = [Images.photos];
I'm trying to add a whole array to useState variable
import React, { Fragment, useState, useEffect } from 'react';
import { Form, Button, Popover, OverlayTrigger } from 'react-bootstrap';
const Filter = props => {
const [formData, setFormData] = useState({
filter: ''
});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci","paris", "london"];
console.log(retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const { filter } = formData;
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
props.filterData(filter);
//Close the Popover
document.body.click();
};
const popover = (
<Popover id="popover-basic">
<Form>
<Form.Group controlId="formGroupEmail">
<Form.Label>Add New Filter</Form.Label>
<Form.Control
type="text"
placeholder="New Filter"
name="filter"
onChange={e => onChange(e)}
/>
</Form.Group>
<Button variant="dark" type="submit" onClick={e => onSubmit(e)}>
Add
</Button>
</Form>
</Popover>
);
return (
<Fragment>
<label>
<p className="filter-title">{props.title}</p>
</label>
<div className={props.className ? props.className : 'filter'}>
{!props.retrievedFilter
? items.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})
: props.retrievedFilter.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})}
<OverlayTrigger
trigger="click"
placement="right"
rootClose
overlay={popover}
>
<p className="text-field">Type new one</p>
</OverlayTrigger>
</div>
</Fragment>
);
};
export default Filter;
however retrievedItems shows as an empty array in the console.
any help would be appreciated.
setState is async. You have to console.log inside an effect hook with the array as a parameter.
useEffect(() => console.log(retrieved_items), [ retrievedItems ])
The second parameter ensures that the effect fires in repose to a change in the values passed to it.
Per my comment, here is a code snippet that I think does what you want.
I couldn't get it running in SO but here's a codepen: https://codepen.io/anon/pen/PrYYmz?editors=1010 (watch the chrome console as you add items)
import React, {
Fragment,
useState,
useEffect
} from 'react';
const Filter = props => {
const [formData, setFormData] = useState({filter: ''});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci", "paris", "london"];
console.log('add', retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const {filter} = formData;
const onChange = e => {
setFormData({ ...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
//props.filterData(filter);
//Close the Popover
document.body.click();
};
return (
<Fragment >
<label >
<p className = "filter-title" > {
props.title
} </p> </label> <
div className = {
props.className ? props.className : 'filter'
} > {!props.retrievedFilter ?
items.map((item, index) => {
return ( <
div className = "filter-text"
key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)}>X</button></div>
);
}) :
props.retrievedFilter.map((item, index) => {
return ( <div className = "filter-text" key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)} >X</button></div>);})} <input type = "text" placeholder = "New Filter" name = "filter" onChange = {e => onChange(e) }/>
<button variant = "dark" type = "submit" onClick = {e => onSubmit(e)} >Add</button>
</div>
</Fragment>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>