I am trying to implement a list composed of an <input> field as well as a delete button, for each item. I also have an Add item button near the list so the user can add new entries.
I am encountering an issue when trying to delete a certain item from the list.
For example:
The initial list
index
input-value
1
a
2
b
3
c
4
d
5
e
After deleting the item at index 3
index
input-value
1
a
2
b
4
c
5
d
As you can observe, the right item is deleted, but the contents are somehow shifted from their initial place and it appears like the last item is always deleted.
I can't figure it out what am I doing wrong.
Here is the parent component:
export default function ListPage() {
const [itemList, setItemList] = useState([]);
const removeItem = (index) => {
console.log(index);
const list = Object.assign([], itemList);
const updatedList = list.filter((object, i) => i != index);
setItemList(updatedList);
console.log(updatedList);
};
const addItem = () => {
const updatedList = [...itemList, <ListElement />];
setItemList(updatedList);
console.log(updatedList);
};
return (
<div className="list-page">
<div className="list-page-title">
<h2>Create your own List</h2>
<br></br>
<h4>Create your own list</h4>
</div>
<input
className="title-input"
type="text"
placeholder="Enter Title"
/>
<div className="list-content">
{itemList.map((data, index) => {
return (
<div>
<ListElement
handleRemove={() => removeItem(index)}
index={data.index}
/>
</div>
);
})}
</div>
<button className="add-button" onClick={addItem}>
<span className="list-page-button-icon">+</span>Add Item
</button>
</div>
);
}
And here is the ListElement component:
export default function ListElement(props) {
return (
<div className="list-item">
<input
className="item-input"
type="text"
placeholder={props.index}
></input>
<MdRemoveCircleOutline
className="remove-button"
onClick={props.handleRemove}
/>
</div>
);
}
Inside your removeItem function, you don't need to do const list = Object.assign([], itemList);; which is messing the order of how the list is stored and rendered.
You already have access to the entire list in the itemList variable you defined before.
So, instead of
const removeItem = (index) => {
console.log(index);
const list = Object.assign([], itemList);
const updatedList = list.filter((object, i) => i != index);
setItemList(updatedList);
console.log(updatedList);
};
So, instead of
const removeItem = (index) => {
const updatedList = itemList.filter((object, i) => i != index);
setItemList(updatedList);
};
Related
So I'm practising react with a simple task master app, where I add each user input as a new task in an array of tasks. In the app, I have Add, Delete, and Update buttons.
Everything seems to be working fine except for the update function, it updates the last index of the array instead of the specific index I clicked.
Here is my JSX
const JsxElement = task.map((eachTask, index) => {
return (
<Fragment key={index}>
<div key={index} className="table-data-container">
<div className="item-data">{eachTask}</div>
<div className="item-data">{date}</div>
<div className="item-data">
<div className="btn-data-container">
<div className="btn-data">
<div className="btn" onClick={() => deleteTask(index)}>Delete</div>
</div>
<div className="btn-data">
<div className="btn" onClick={() => UpdateTaskBtn(eachTask, index)}>Update</div>
</div>
</div>
</div>
</div>
<br/>
{task.length - 1 === index &&
<div className="input-update-container">
<div className="input-area">
<input
ref={inputRef}
type="text"
/>
</div>
<div className="btn-update-add-container">
{update ?
<div className="btn-add" onClick={() => handleTaskUpdate(eachTask, index)}>Update
Task</div>
:
<div className="btn-add" onClick={handleTask}>Add Task</div>
}
</div>
</div>
}
</Fragment>
)
})
The first update button function prepares the input, sets the task to be updated and makes the update button visible. The second one is where I want the update action to happen once clicked.
function UpdateTaskBtn(eachTask) {
inputRef.current.value = eachTask
setUpdate(true)
}
function handleTaskUpdate(e, index) {
const list = [...task]
list[index] = inputRef.current.value
setTask(list)
inputRef.current.value = ""
setUpdate(false)
}
I want to be able to set the task to the specific index I want to update.
based on this line of code
{task.length - 1 === index &&
you are checking if the index is the last index , so you are passing the last index to the handleTaskUpdate function. so you can define a updateIndex state
const [updateIndex,setUpdateIndex] = useState()
then your function should look like this
function UpdateTaskBtn(eachTask,index) {
inputRef.current.value = eachTask
setUpdateIndex(index)
setUpdate(true)
}
function handleTaskUpdate(e, index) {
const list = [...task]
list[updateIndex] = inputRef.current.value
setTask(list)
inputRef.current.value = ""
setUpdate(false)
}
do not forget to pass index to UpdateTaskBtn function in onClick event
Try this
function handleTaskUpdate(e, index) {
const value = inputRef.current.value
const updatedTasks = task.map((task, i) => i === index ? value : task)
setTask(updatedTasks)
inputRef.current.value = ""
setUpdate(false)
}
I am following a tutorial exercise and I got the following error
Objects are not valid as a React child
I know this error is related to the object as I am trying to access the object but it needs an individual item of an object but not sure.
Why cannot the map loop over each item in the array?
Following is my code
var template = <h1>Indecision App</h1>;
var app = {
title: 'Indecision App',
subtitle: 'yo',
options: []
}
let count = 0;
function checkSubtitles (subtitle){
if(subtitle){
return <p>{subtitle}</p>
}else{
return undefined
}
}
function reset(){
count = 0;
reRenderApp();
}
function increaseCount(){
count++;
reRenderApp();
}
function onSubmitHandle(e){
e.preventDefault();
const options = e.target.elements.options;
app.options.push(options);
reRenderApp();
e.target.elements.options.value = ''
}
function removeAll(){
app.options = [];
reRenderApp();
}
function reRenderApp(){
var templateTwo = (
<div>
<h1>{app.title}</h1>
{checkSubtitles(app.subtitle)}
<p>Count: {count}</p>
<p>Array Length: {app.options.length > 0 ? app.options.length : '0 Items'}</p>
<ol>
{app.options.map((item)=>{
return <li key={item}>{item}</li>
})}
</ol>
<hr></hr>
<form onSubmit={onSubmitHandle}>
<input type="text" name="options" />
<input type="submit" value="Push to the Array" />
<input type="reset" value="Empty my list" onClick={removeAll} />
</form>
<button onClick={()=>{
increaseCount();
}}>Increase Count</button>
<button onClick={()=>{
reset();
}}>Reset Count</button>
</div>
)
ReactDOM.render(templateTwo, appRoot)
}
var appRoot = document.getElementById('app');
reRenderApp();
<body>
<div id="app"></div>
<script src="https://unpkg.com/react#16.0.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.0.0/umd/react-dom.development.js"></script>
<script src="./app.js"></script>
</body>
</html>
The main problem is, as you mentioned: Objects are not valid as a React child
But, what is happening?
If we go into:
function onSubmitHandle(e){
e.preventDefault();
// Line 1
const options = e.target.elements.options;
app.options.push(options);
reRenderApp();
// Line 2
e.target.elements.options.value = ''
}
So in Line 1, you're pushing options into the options array.
But, then in Line 2, we can notice options has an attribute (so, it's an object)
So, if you change Line 1, from:
const options = e.target.elements.options;
To this:
const options = e.target.elements.options.value;
It'd work.
Also, to check what I'm saying you have 2 options:
option 1: console.log
function onSubmitHandle(e){
e.preventDefault();
const options = e.target.elements.options;
console.log({ options })
app.options.push(options);
reRenderApp();
e.target.elements.options.value = ''
}
option 2: make that option a valid child of react with JSON.stringify()
<ol>
{app.options.map((item, index)=>{
return <li key={index}>{JSON.stringify(item)}</li>
})}
</ol>
You can do
{app.options.length && app.options.map((item)=>{
return <li key={item}>{item}</li>
})}
But you must be sure that "item" here is not an object as you can't render an object
The reason for this is that your options array is going to be filled with elements as you're pushing the input element with the name of "option" into your array - this elements are objects in JS which you can't render out as list items.
Use React State to store anything that's going to change in the UI - in this case your list of options> So rather than doing
var app = {
title: 'Indecision App',
subtitle: 'yo',
options: []
}
let count = 0;
Do:
const [options, setOptions] = React.useState([]);
const [count, setCount] = React.useState(0);
Title and subtitle are probably not going to change, so just put them in h1 & h2 elements - if they are, then use the state pattern again.
Get rid of the two inputs with types of "submit" & "reset" just use normal button elements instead.
You'll also need an onchange event on your input where the text will go in and each time the onchange event is fired (i.e, when a user types) you'll need to save the input text
const [inputText, setInputText] = React.useState('');
const handleChange = (e) => {
const {value} = e.target;
setInputText(value)
}
<input type="text" value={inputText} onChange={handleChange}/>
Then in your onHandleSubmit function, just have
const onHandleSubmit = () => {
setOptions([...options, inputText]);
setInputText('')
}
This should work
There is two screen. First picture is show list. If clicked Button which is next to search Button, second picture is showed. Second is select filter. When user select options in filter and clicked "적용" Button, list will be changed.
And there code is here.
...
const [modalOpen, setModalOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(6);
const [area] = useState(location.state.area)
const [city] = useState(location.state.city)
// all list
const locList = restList.filter(
key => (key.address.includes(area) && key.address.includes(city))
);
//about Modal
const openModal = () => {
setModalOpen(true);
}
const closeModal = () => {
setModalOpen(false);
}
...
let target;
let foodList = [[], []]
const sel_Food = (e) => {
if (e.target.tagName === "BUTTON") {
target = e.target.innerText;
if (!foodList.includes(target))
foodList[0].push(target)
} else {
target = e.target.parentNode.nextSibling.innerText;
foodList[1] = target
}
}
//make list
const list = (foodList) => {
let result = [];
if (foodList[0].length === 1) {
result = locList.filter(key => key.foodType.includes(foodList[0][0]))
} else {
for (let i = 0; i < foodList[0].length; i++) {
let tmp = locList.filter(key => key.foodType.includes(foodList[0][i]))
result.push(...tmp)
}
}
if (foodList[1].length != 0) {
result = result.filter(key => key.friendly.includes(foodList[1]))
}
return result
}
return (
<div>
... {
(locList.length === 0)
? ...
: <div>
<div className="btn_class_list">
...
<React.Fragment>
<button className="button_filter" onClick={openModal}><img className="icon_filter" src={icon_filter} alt="icon_filter"/></button>
<Modal_Restaurant open={modalOpen} close={closeModal} header="필터">
<p className="langoption">음식 종류</p>
<div>
...
</div>
<p className="langoption">Halal Standard</p>
<div className="foodtype">
...
</div>
</Modal_Restaurant>
</React.Fragment>
</div>
<Item rlist={currentPosts(locList)} moveTo="restaurant" area={area} city={city}></Item> //make List
<Pagination start={numOfFirst} last={numOfLast} paginate={setCurrentPage}></Pagination>
</div>
}
</div>
)
sel_Food function record option that user selected.
list function make sub list for delivered to Item component.
But I don't know how to delivered array result in list function with "적용" button.
Thank you.
As I understand from the question, you need to change the list of items you display according to the filters the user applies. It can be done with the state. As a start, you can try to update the list of items through a filter function and use the state variable to render the filtered results. You'd asked about returning a value when a button is clicked. Buttons have an onClick handler in React. You can pass it a function like this:
<Button onClick={handleClick} />
or if your function takes any arguments:
<Button onClick={() => handlClick(args)} />
I have input feild which takes a input (interest) from user and after hitting the Enter key adds the interest to the interests array. Then the elements in this array are displayed on screen via the Domain component as the user goes on adding. The Domain component contains an icon X (cross) which on click should delete the selected/clicked element from the array. Right now the last element in the array is getting removed after clicking.
How can I resolve this? Here is the code:
function Demo() {
const [interest, setinterest] = useState("");
const [interests, setinterests] = useState([]);
const domainSelection = (e) => {
if (e.key === "Enter" && interest.length > 0) {
setinterests((interests) => [...interests, interest]);
setinterest("");
}
};
const RemoveDomain = (e) => {
var arr = [...interests];
var index = arr.indexOf(e.target.value);
arr.splice(index, 1);
setinterests(arr);
};
const Domain = ({ interest }) => {
return (
<span>
{interest}
<span>
<X onClick={RemoveDomain} />
</span>
</span>
);
};
return (
<div>
<Input
name="intersts"
type="text"
placeholder="eg Machine Learning .. "
value={interest}
required={true}
onChange={(e) => setinterest(e.target.value)}
className="interest-input inputs"
onKeyDown={domainSelection}
/>
{interests.map((interest, i) => (
<Domain
interest={interest}
// Prevent duplicate keys by appending index:
key={interest + i}
/>
))}
</div>
);
}
export default Demo;
I think e.target.value is undefined.
Use Filter, This might help
const RemoveDomain = (value) => {
var arr = interests.filter((item) => item !== value);
setinterests(arr);
};
const Domain = ({ interest }) => {
return (
<span>
{interest}
<span>
<X onClick={() => RemoveDomain(interest)} />
</span>
</span>
);
};
I'm having some issues updating a select tag value. When I click on it and try changing the value it logs the new value to the console, but it's value is not changing no matter how many times I click a different option. But when I make some changes in the code editor and save it it display the new value but if I try againg changing the value clicking on it it does not change.
const quantityChange = (e) => {
console.log('changing quantity')
const newCart = shoppingCart
console.log(
"new cart variable created. It's value is " + JSON.stringify(newCart)
)
const index = e.target.dataset.index
console.log('Item index is ' + index)
newCart[index]['quantity'] = e.target.value
console.log(
'The new quantity for the item is ' + newCart[index]['quantity']
)
setShoppingCart(newCart)
console.log(
'new value set for shopping cart. ' +
JSON.stringify(shoppingCart)
)
}
const ShoppingCart = ({ shoppingCart, quantityChange, removeFromCart }) => {
const onSubmit = (e) => {
e.preventDefault()
}
if (shoppingCart.length === 0) {
return <div>There's no items in the shopping cart</div>
}
const options = []
for (let i = 0; i <= 10; i++) {
options.push(
<option name="quantity-1" value={i} key={'qsfkajlf' + i}>
{i}
</option>
)
}
return (
<div>
<h1>
There's {shoppingCart.length} item{shoppingCart.length > 1 && 's'} in
your shopping cart.
</h1>
{shoppingCart.map((item, index) => {
const { id, name, price, image, quantity } = item
return (
<div key={id}>
<div className="flex-container">
<div className="img-container">
<img src={image} alt="" />
</div>
<div className="item-info">
<h2>{name}</h2>
<p>${price}</p>
</div>
<div className="remove-quantity">
<div className="quantity">
<form onSubmit={onSubmit}>
{/* TODO: don't allow more than 10 items */}
<select
id="quantity"
data-index={index}
onChange={quantityChange}
value={quantity}
>
{options}
</select>
</form>
</div>
<div className="remove-container">
<button data-remove={index} onClick={removeFromCart}>
x
</button>
</div>
</div>
</div>
</div>
)
})}
</div>
)
}
It isn't updating because you are doing this:
const quantityChange = (e) => {
console.log('changing quantity')
const newCart = shoppingCart // <-- copy a reference to shoppingCart
// do stuff
setShoppingCart(newCart) // <-- write the original (modified) object back in
}
As the object reference doesn't change, the re-render isn't triggered.
Instead, you just need to copy your object (instead of just copying the reference):
const quantityChange = (e) => {
console.log('changing quantity')
const newCart = {...shoppingCart} // <-- copy the contents into a new object