React how to change an individual button in a list - javascript

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

Related

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

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

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

Delete item at selected index when clicking delete button - React

I'm currently having an issue where when I click on the delete button for a particular element it only deletes the first item in list array every time rather than the selected one.
Any help with this would be greatly appreciated! :)
import "./css/App.css";
import React, { useRef } from "react";
function App() {
const [item, setItem] = React.useState("");
const [list, updateList] = React.useState([]);
const toDoItem = (item) => {
let newItem = { text: item.target.value, key: Date.now() };
setItem(newItem);
};
const addItem = () => {
updateList((prevState) => [...list, item]);
document.getElementById("Input").value = "";
};
const deleteItem = (index) => {
updateList((prevState) => {
let items = [...prevState];
console.log(items);
items.splice(index, 1);
return items;
});
};
return (
<div className="App">
<h2>Type a todo item you want to complete</h2>
<input
type="text"
placeholder="Add a todo..."
id="Input"
onChange={toDoItem}
/>
<button id="Add" onClick={addItem}>
Add
</button>
{list.map((item, index) => {
return (
<ol key={index}>
{item.text[0].toUpperCase() + item.text.slice(1).toLowerCase()}
<button>Edit</button>
<button id="Delete" onClick={deleteItem}>
✗
</button>
{/* <button onClick={test}>test</button> */}
</ol>
);
})}
</div>
);
}
export default App;
You forgot pass index when call deleteItem
onClick={() => deleteItem(index)}

How to make Multi select without using bootstrap and with only pure css?

Now, I am making a multi-select component that has a heading inside the select box.
I made the tag inside the div component and every item is tag.
This is my code.
import React, { useState, useRef, useEffect } from "react";
import "./style.css";
function Select({ title, data, changeSelect, selectedItem }) {
const [categories, setCategories] = useState(data);
const [selectedItems, setSelectedItems] = useState([]);
const wrapperRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const handleClickOutside = event => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
setIsVisible(false);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, false);
return () => {
document.removeEventListener("click", handleClickOutside, false);
};
}, []);
const selectItem = (item) => {
if (selectedItems.indexOf(item) === -1) {
if (categories.length === 1) {
}
setCategories((prevCategories) =>
prevCategories.filter((value) => value !== item)
);
setSelectedItems((prevItems) => [...prevItems, item]);
}
};
const removeItem = (item) => {
setSelectedItems((items) => items.filter((value) => value !== item));
setCategories((prevCategories) => [...prevCategories, item]);
};
return (
<div className="custom-select" ref={wrapperRef} >
{/* <label className="append-label">{title}</label> */}
<div className="multi-select" >
<title>{title}</title>
<div className="multi-select-wrapper">
{selectedItems.length > 0 &&
selectedItems.map((item) => (
<span onClick={() => removeItem(item)}>{item}</span>
))}
</div>
</div>
<div className={`dropDown-wrapper ${isVisible ? "active" : ""}`}>
<ul>
{categories.length > 0 &&
categories.map((item) => (
<li onClick={() => selectItem(item)}>{item}</li>
))}
<li
style={{
display: `${categories.length === 0 ? "block" : "none"}`,
}}
>
No Result
</li>
</ul>
</div>
</div>
);
}
export default Select;
When I click the select button, it shows a dropdown box.
The most important thing here is that I used react-onclickoutside npm library to get the click event outside the current element but it is not working well.
I used several libraries but all of them do not work at all.
PS: I have to use 3 multi-selects.
react: 16.14.0
react-scripts: 3.4.3
react-onclickoutside: 6.10.0
I had a solution by using another npm library and stopPropagation() function.
import React, { useState, useRef, useEffect } from "react";
import ClickOutside from "react-click-outside";
import "./style.css";
function Select({ title, data, changeSelect, selectedItem }) {
const [categories, setCategories] = useState(data);
const [selectedItems, setSelectedItems] = useState([]);
const [isActive, setIsActive] = useState(false);
// const onClickSelect = (e, active) => {
// setIsActive(active);
// console.log(e.target.value, "asdfasdfasdfs");
// };
const selectItem = (item) => {
if (selectedItems.indexOf(item) === -1) {
if (categories.length === 1) {
setIsActive(false);
}
setCategories((prevCategories) =>
prevCategories.filter((value) => value !== item)
);
setSelectedItems((prevItems) => [...prevItems, item]);
}
};
const removeItem = (e, item) => {
e.stopPropagation();
setSelectedItems((items) => items.filter((value) => value !== item));
setCategories((prevCategories) => [...prevCategories, item]);
};
return (
<ClickOutside onClickOutside={() => setIsActive(false)}>
<div className="custom-select" onClick={() => setIsActive(!isActive)}>
<div className="multi-select">
<title>{title}</title>
<div className="multi-select-wrapper">
{selectedItems.length > 0 &&
selectedItems.map((item) => (
<span onClick={(e) => removeItem(e, item)}>{item}</span>
))}
</div>
</div>
<div className={`dropDown-wrapper ${isActive ? "active" : ""}`}>
<ul>
{categories.length > 0 &&
categories.map((item) => (
<li onClick={() => selectItem(item)}>{item}</li>
))}
<li
style={{
display: `${categories.length === 0 ? "block" : "none"}`,
}}
>
No Result
</li>
</ul>
</div>
</div>
</ClickOutside>
);
}
export default Select;
I hope this will help you who has same issue with me.

How to toggle boolean specific states?

I want to add to Chip an startIcon={<Icon />}
when click on a Chip.
The state of the icon is managed by chipsState.
In this code,
the state of all chips would change.
How can I change only the chipsState of the element that is clicked?
In this code, the state of all chips will change.
How can I change only the chipsState of the element that is clicked?
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
const [chipsState, setChipsState] = useState(false);
const onChipClick = (element:any) => {
setChipsState(chipsState => !chipsState);
}
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<Chip onClick={() => onChipClick(element)} startIcon={chipsState && <Icon />}>{element.description}</Chip>
</div>
))}
</div>
);
}
export default Modal;
To handle local state (and better testing), you should create a new custom Chip component with dedicated chipState.
interface CustomChipProps {
description: string
}
const CustomChip = (props: CustomChipProps) => {
const [chipState, setChipState] = useState(false);
return <Chip onClick={() => setChipState(prev => !prev)} startIcon={chipState && <Icon />}>{props.description}</Chip>;
}
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<CustomChip description={element.description} />
</div>
))}
</div>
);
}
export default Modal;
You can achieve your desired output by changing chipState state from boolean to object.
So first let's change to object state instead of boolean
const [chipsState, setChipsState] = useState({});
Now we will change onChipClick function to change value of selected chip state
const onChipClick = (element:any) => {
setChipsState({...chipsState, chipsState[element]: !chipsState[element]});
}
And finally we will read correct value of each chipsState element.
<Chip onClick={() => onChipClick(element)} startIcon={chipsState[element] && <Icon />}>{element.description}</Chip>
You can try like the following
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
const ChipSet = ({ symtomsData }) => {
const data = symtomsData.map((symtom) => ({ ...symtom, isSelcted: false }));
const [chipSets, setChipSets] = useState(data);
const onSelectChipSet = useCallback(
(e, index) => {
const updatedChipSets = chipSets.map((chip, i) =>
i === index ? { ...chip, isSelcted: e.target.checked } : chip
);
setChipSets(updatedChipSets);
},
[chipSets]
);
console.log("chipSets", chipSets);
return (
<div>
<h1>Symtoms Data</h1>
{chipSets.map((x, i) => (
<div key={i}>
<label>
<input
onChange={(e) => onSelectChipSet(e, i)}
type="checkbox"
value={x.isSelcted}
/>
{x.description}
</label>
</div>
))}
</div>
);
};
class App extends React.Component {
render() {
const symtomsData = [
{
description: "mild"
},
{
description: "cold"
}
];
return (
<Grid>
<Row>
<ChipSet symtomsData={symtomsData} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

Categories