Apply increment/decrement count on adding/removing divs with React Hooks - javascript

I'm trying to add and remove a count every time the div is clicked and either added or removed from the list, So far I have been able to add the increment count when the div is clicked but it still adds the count even when the name has already been clicked and added to the list. I have placed the incrementCount() in the incorrect place and also I have not been able to work out where to add the decrementCount() to. This should be easy for most people. and many thanks in advance if you could help out or point me in the right direction 😀 the Link to sandbox is here ➡️ https://codesandbox.io/s/optimistic-hamilton-len0q?file=/src/Home.js:244-258
import { useEffect, useState } from "react";
export const List = (props) => {
const [selectedNames, setSelectedNames] = useState([]);
const [names, setNames] = useState([]);
const [count, setCount] = useState(0);
const decrementCount = () => {
if (count > 0) setCount(count - 1);
};
const incrementCount = () => {
setCount(count + 1);
};
useEffect(() => {
props.title === "" && setNames(props.items);
}, [setNames, props]);
return (
<div className="">
<div className="">
⬇ Click on the names below to add them to the list
{names &&
names.map((item, index) => (
<div
key={`${props}-${index}`}
onClick={() => {
incrementCount();
!selectedNames.includes(item) &&
setSelectedNames((oldValue) => [...oldValue, item]);
}}
>
<div className="list-name">{item.name}</div>
</div>
))}
</div>
<div className="count-box">
{count}
<span>selected</span>
</div>
<div
className="unselect-all-box"
onClick={() => {
setSelectedNames([]);
setCount(0);
}}
>
Unselect all
</div>
{selectedNames &&
selectedNames.map((format) => (
<div key={format.id}>
<div className="">{format.name}</div>
<div
className="remove-selected"
onClick={() => {
setSelectedNames(
selectedNames.filter((f) => f.name !== format.name)
);
}}
>
(Press HERE to remove name)
</div>
</div>
))}
</div>
);
};
export default List;
export const App = (props) => {
const formats = [
{
id: "0001",
name: "(1) Sam Smitty",
},
{
id: "0002",
name: "(2) Hong Mong",
},
];
return (
<div>
<List title="" items={formats} />
</div>
);
};
export default App;```

I think you are over-complicating things a bit. From what I can tell, the count state is just "derived state" from the selectedNames array length. There's really no need to increment/decrement a selected names count when you can just count the length of the selectedNames array.
Remove the increment/decrement handlers and use the selectedNames array length. By deriving the selected count there's no need to count anything manually.
<div className="count-box">
{selectedNames.length}{" "}
<span>selected</span>
</div>
export const List = (props) => {
const [selectedNames, setSelectedNames] = useState([]);
const [names, setNames] = useState([]);
useEffect(() => {
props.title === "" && setNames(props.items);
}, [setNames, props]);
return (
<div className="">
<div className="">
⬇ Click on the names below to add them to the list
{names?.map((item, index) => (
<div
key={`${props}-${index}`}
onClick={() => {
!selectedNames.includes(item) &&
setSelectedNames((oldValue) => [...oldValue, item]);
}}
>
<div className="list-name">{item.name}</div>
</div>
))}
</div>
<div className="count-box">
{selectedNames.length} <span>selected</span>
</div>
<div
className="unselect-all-box"
onClick={() => {
setSelectedNames([]);
}}
>
Unselect all
</div>
{selectedNames?.map((format) => (
<div key={format.id}>
<div className="">{format.name}</div>
<div
className="remove-selected"
onClick={() => {
setSelectedNames(
selectedNames.filter((f) => f.name !== format.name)
);
}}
>
(Press HERE to remove name)
</div>
</div>
))}
</div>
);
};

Increment the count only if the item is not in the array
onClick={() => {
if(!selectedNames.includes(item)){
incrementCount();
setSelectedNames((oldValue) => [...oldValue, item]);
}
}}

Related

Handle rendering array of editable components

I have a contentEditable component:
EditableComponent.js
const EditableComponent = (props) => {
return <p contentEditable>{props.children}</p>;
};
In the ParentComponent I can add EditableComponents to an array (someArr) with useState, and then I pass someArr and setSomeArray via props to another component (AllEditable) to render it:
ParentComponent.js
import EditableComponent from "./components";
import AllEditable from "./components";
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setContentArr((prevContentArr) => {
return [...prevContentArr, <EditableComponent />];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
Each rendered component (EditableComponent) have a span with the content 'X' that should delete the target onClick:
AllEditable.js
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.filter((_, idx) => idx !== index);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
);
})}
</div>
);
};
The problem:
It doesn't matter which component I'm trying to delete, it removes the last component (even in the Components section in the developer tools) and I'm pretty sure that the logic behind deleting (filter) works well.
I tried deleting the contentEditable attribute, and added some unique random text in each component and then it worked as expected!.
Things I tried
Creating a new array without the removed target
Nesting the components in someArr in objects with key: index, example: {idx: 0, content: <EditableComponent />}
Added a function - onDoubleClick for each EditableComponent to toggle the attribute contentEditable, true or false.
Replaced the element in EditableComponent to <textarea></textarea> instead of <p contentEditable></p>
Your problem is all your EditableComponent components have the same key (because you haven't declared key on them). React cannot identify which EditableComponent you want to delete. You can check this document.
I'd suggest you add a key attribute like below
<EditableComponent key={prevContentArr.length - 1}/>
For safer index reservation, you should use map instead filter
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.map((x, idx) => idx !== index ? x : null);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return content ? (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
) : null;
})}
</div>
);
};
const { useState } = React
const EditableComponent = (props) => {
return <p contentEditable>{props.children}</p>;
};
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.map((x, idx) => idx !== index ? x : null);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return content ? (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
) : null;
})}
</div>
);
};
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setSomeArr((prevContentArr) => {
return [...prevContentArr, <EditableComponent key={prevContentArr.length - 1}/>];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
ReactDOM.render(
<ParentComponent/>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Side note that keys with index values are not the best because your array keeps changing that would make key changes as well. You can use some unique key generator to handle that situation instead.
You shluld change the way you set keys, setting the element key to: "content-index" is confusing for react, because once you remove an item all the indexes will change, and therefore your keys.
So you need to find a way to have static keys for your elements. That way everytime they render, the key will be the same.
The logic is working correctly and in fact it is deleting the correct elements, however since you are using the index to identify elements, you will never see this since it will always appear that only the last one is removed (when the array updates, the indices will update too).
So if you had 0,1,2 and removed 1 now you have an array with indices 0,1
To test this you can place a reference to the index when rendering the content editable div, based on testing you can see that the correct element is in fact being removed:
import "./styles.css";
import React, { useState } from "react";
const EditableComponent = (props) => {
return (
<p contentEditable>
{props.children}
{props.idtfy}
</p>
);
};
const AllEditable = (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.filter((_, idx) => idx !== index);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return (
<div key={`content-${idx}`}>
<span>{idx}</span>
<span
onClick={() => {
deleteContentHandler(idx);
}}
>
X
</span>
<div>{content}</div>
</div>
);
})}
</div>
);
};
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setSomeArr((prevContentArr) => {
return [
...prevContentArr,
<EditableComponent idtfy={prevContentArr.length + 1} />
];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
That said, your key should probably point to some static value, but that will only impact rendering order, not which item is being clicked/closed.

I'm trying to display icons by category, and now I want users to be able to click on an icon, and

it should be added to the list, but the problem is that I've created so many mini files to display icons that I have no idea how to accomplish the task.
export const FileterIcone = (props) => {
const Car = CarIcons.filter((value) => { return (value.Type.includes(props.input)) })
return (
<div className="container cus-animation cus-bg" >
<div className="row row-cols-1 row-cols-md-3 g-4">
{Car.length !== 0 && Car.map((value) => { return (<IconCard key={value.id} name={value.Name} url={value.URL} onClick={PostSlecter} />) })}
</div>
</div>)
}
export const FileterByRadioBtn = (props) => {
const Car = CarIcons.filter((value) => { return (value.Name.includes(props.Radio)) })
return (
<div className="container cus-animation cus-bg" >
<div className="row row-cols-1 row-cols-md-3 g-4">
{Car.length !== 0 && Car.map((value) => { return (<IconCard key={value.id} name={value.Name} url={value.URL} onClick={PostSlecter} />) })}
</div>
</div>)
}
export const PostSlecter = (event) => {
const [isSelected, setSelected] = useState([])
const selected = event.target
console.log(selected)
if (selected) {
setSelected((add) => [...add, selected])
}
return (
<div className="mini-scrol-item">
{isSelected.length !== 0 && isSelected.map((value) => { return (<HomeItemList name={value} />) })}
</div>
)
}
As you can see in the code above, I've created three functions: the first two are for filtering, and the third is for the postselecter handler function, which should be called when the user clicks on an icon. Please advise me on how to proceed.

How to change the icon of only one particular item of mapped array in reactjs?

I was creating the functionality of pinning and unpinning of particular note, so when the user clicks the thumbtack icon I want that icon of only that particular note changes to a cross icon but when I am clicking on the second notes to pin it then the icon that changed on previous pinned note gets restored to its original form.
I have created the pinning functionality using onPin function but struggling with changing the icon of that particular pinned item.
I want to add icons to pinned items in such a way that previously added close icons stay in their place and do not get updated.
What I tried?
So i created the state variable iconId which is an array so whenever the user clicks pinned icon then new id will be pushed to the iconId array and while displaying the output I put the condition that if the current id is included in iconId array then change icon of all those respective ids in iconId to cross icon, apparently this functionality dint work.
-----------------------App.js--------------------------------
import React, { useState } from "react";
import './App.css';
import Input from './Components/Input';
import Navbar from './Components/Navbar';
import Notesview from './Components/Notesview';
import Notesdata from "./Data/Notesdata";
function App() {
const [data, setData] = useState(Notesdata);
// const [pin, setpin] = useState(true)
const [iconId, seticonId] = useState([])
function handleDelete(id) {
let newData = data.filter((item) => item.id !== id)
setData(newData)
console.log(newData)
console.log(Notesdata)
console.log(0)
}
function handlePost(value) {
// Notesdata.push(value)
// setData(Notesdata)
// // console.log(typeof data)
// console.log(Notesdata)
setData([...data, value]);
}
function onPin(id) {
let index = data.map((item) => {
return item.id
}).indexOf(id)
let arr1 = data.slice(0, index).concat(data.slice(index + 1))
arr1.unshift(data[index])
setData(arr1);
seticonId([...iconId] , id)
console.log(iconId)
}
function handleclose() {
// setpin(!pin)
// seticonId("")
}
return (
<div className="App">
<header className="App-header">
<Navbar />
<Input data={data} handlePost={(value) => handlePost(value)} />
<Notesview handleDelete={handleDelete} Data={data} onPin={onPin} iconId={iconId} handleclose={handleclose} />
</header>
</div>
);
}
export default App;
----------------Noteview function(mapping function)---------------
import React from 'react'
import Notescard from './Notescard'
import "../Styles/Notes.css"
// import { useState } from 'react'
const Notesview = ({ Data, handleDelete, onPin , iconId, handleclose}) => {
return (
<>
<div className='notes'>
{Data && Data.map((item) => {
return <Notescard item={item} handleDelete={handleDelete} onPin={onPin} iconId={iconId} key={item.id} handleclose={handleclose}/>
})
}
</div>
</>
)
}
export default Notesview
-----------------------------Notescard component------------------
import React from "react";
import "../Styles/Notescard.css";
import { FaThumbtack, FaTrashAlt, FaPencilAlt ,FaTimesCircle} from "react-icons/fa";
// import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
const Notescard = ({ item , handleDelete,onPin,iconId,handleclose, key}) => {
return (
<>
<div className="box">
<div className="content">
<h2 className="item1">{item.title}</h2>
<h4 className="item1"> {item.tagline}</h4>
<p className="item2">{item.description}</p>
</div>
<div className="icons">
{iconId.includes(item.id) ? <FaTimesCircle onClick={handleclose}/> : <FaThumbtack id={item.id} onClick={() => onPin(item.id)}/> }
<FaTrashAlt onClick={() => handleDelete(item.id)}/>
<FaPencilAlt />
</div>
</div>
</>
);
};
export default Notescard;
Issue
You are passing two arguments to the seticonId state updater function.
seticonId([...iconId], id)
The id is never added to the iconId state.
Solution
Use a functional state update to append the id to the array.
seticonId((iconId) => iconId.concat(id));
Code:
const Notescard = ({ item, handleDelete, onPin, iconId, handleclose }) => {
return (
<div className="box">
<div className="content">
<h2 className="item1">{item.title}</h2>
<h4 className="item1"> {item.tagline}</h4>
<p className="item2">{item.description}</p>
</div>
<div className="icons">
{iconId.includes(item.id) ? (
<FaTimesCircle onClick={() => handleclose(item.id)} />
) : (
<FaThumbtack id={item.id} onClick={() => onPin(item.id)} />
)}
<FaTrashAlt onClick={() => handleDelete(item.id)} />
<FaPencilAlt />
</div>
</div>
);
};
...
const Notesview = ({ Data, handleDelete, onPin, iconId, handleclose }) => {
return (
<div className="notes">
{Data.map((item) => {
return (
<Notescard
item={item}
handleDelete={handleDelete}
onPin={onPin}
iconId={iconId}
key={item.id}
handleclose={handleclose}
/>
);
})}
</div>
);
};
...
export default function App() {
const [data, setData] = useState(Notesdata);
const [iconId, seticonId] = useState([]);
function handleDelete(id) {
let newData = data.filter((item) => item.id !== id);
setData(newData);
console.log(newData);
console.log(Notesdata);
console.log(0);
}
function handlePost(value) {
setData([...data, value]);
}
function onPin(id) {
setData((data) => {
const index = data.findIndex((item) => item.id === id);
const arr1 = data.slice(0, index).concat(data.slice(index + 1));
arr1.unshift(data[index]);
return arr1;
});
seticonId((iconId) => iconId.concat(id));
}
function handleclose(id) {
setData((data) => {
const index = data.findIndex((item) => item.id === id);
const insertIndex = data.findIndex((item) => !iconId.includes(item.id));
const arr1 = data.slice(0, index).concat(data.slice(index + 1));
arr1.splice(insertIndex - 1, 0, data[index]);
return arr1;
});
seticonId((iconId) => iconId.filter((elId) => elId !== id));
}
return (
<div className="App">
<Input data={data} handlePost={(value) => handlePost(value)} />
<Notesview
handleDelete={handleDelete}
Data={data}
onPin={onPin}
iconId={iconId}
handleclose={handleclose}
/>
</div>
);
}

React - filter deletes all items instead of one

I fail to understand why .filter deletes all items instead of one even though the key should be unique. Why? I think it should filter out the id that is in the function, but it keeps deleting everything, because of the .map, right?
const App = () => {
const [state, setState] = useState("")
const [list, updateList] = useState([])
const addItem = () => {
list.push(state)
setState('')
updateList(list)
}
const removeItem = (id) => {
updateList(list.filter((item) => item.id !== id));
}
return (
<div>
<input placeholder='type...' value={state} onChange={(e) => setState(e.target.value)}></input>
<button onClick={addItem}>Submit</button>
{list.map((item) => (
<>
<div key={item.id}>{item}</div>
<button onClick={() => removeItem(item.id)}>delete</button>
</>
)
)}
</div>
)
}
export default App
You dont have item.id actually, you just have item which is a string.
You add items to that array from the text box, so you only have Array of strings
What you should do is remove by item not item.id
your code should be something like that
remoteItem function :
const removeItem = (selectedItem) => {
updateList(list.filter((item) => item !== selectedItem));
};
Then in your list.map
{list.map((item) => (
<>
<div key={item}>{item}</div>
<button onClick={() => removeItem(item)}>delete</button>
</>
))}

React how to change an individual button in a list

I have a list that renders content from a base, each list item has a "Favorite" button to move the item to the start of the render. The problem is that I do not know how to change the icon of an individual element when I click on the "Favorite" (asterisk) button.
I tried to do it through LocalState, but because of this, when I click on a separate button "Add to favorites", everything changes at once
Before pressing
After
Code
import { useState } from "react"
const WaysItem = (props) => {
let [change, setChange] = useState(false)
return props.directionsToRender.map((item, index) => (
<li
className={`ways-item`}
key={index}
onClick={() => {
props.getCurrentDirections(index)
}}
>
<div>
<h5>{item.title}</h5>
</div>
<div className="ways-kilometrs">
<div>{item.direction.routes[0].legs[0].distance.text}</div>
<div>{item.direction.routes[0].legs[0].duration.text}</div>
</div>
<button onClick={() => setChange((change) => !change)} key={index}>
<i className={`${!change ? "far" : "fas"} fa-star`}></i>
</button>
</li>
))
}
export default WaysItem
You can have array as a state . And add or remove index on button click.
import { useState } from "react"
const WaysItem = (props) => {
let [selectedItems, setSelectedItems] = useState([])
const onButtonClick = (index) => {
// if index is already there in the selectedItems then remove it
if(selectedItems.includes(index)){
setSelectedItems(selectedItems.filter(item => item !== index))
} else {
setSelectedItems(prevSelectedItems => [...prevSelectedItems, index]);
}
}
return props.directionsToRender.map((item, index) => {
const showStar = selectedItems.includes(index);
return (
<li
className={`ways-item`}
key={index}
onClick={() => {
props.getCurrentDirections(index)
}}
>
<div>
<h5>{item.title}</h5>
</div>
<div className="ways-kilometrs">
<div>{item.direction.routes[0].legs[0].distance.text}</div>
<div>{item.direction.routes[0].legs[0].duration.text}</div>
</div>
<button onClick={() => onButtonClick(index)} key={index}>
<i className={`${showStar ? "far" : "fas"} fa-star`}></i>
</button>
</li>
)})
}
export default WaysItem

Categories