to unique useState variable for repeated item in ReactJS - reactjs

I am newbie in reactjs. How can set a variable for every single iterating items?
I have some list and when click trash icon i want to show loading icon just for clicked item
const [deleteLoading, setDeleteLoading] = useState(false);
<ul className="mt-2">
{projects && projects.map((project, index) => (
<li key={index}>
<NavLink to={`someurl`}>{project.project_name}</NavLink>
<span className={`${deleteLoading ? "" : "hidden"}`}>
<svg> ..loading icon.. </svg>
</span>
<span onClick={() => deleteProject(project._id)} className={`${deleteLoading ? "hidden" : ""}`}>
<svg> ..trash icon.. </svg>
</span>
</li>)
)}
</ul>
when i clicked trash button it seems like
const deleteProject = async (id) => {
setDeleteLoading(true)
// some deleting code..
}

The behaviour you have is normal here because you use the same logic for every elements in your map function : ${deleteLoading ? "" : "hidden"}
If you want to have the delete icon only in the good project you should instead define a state var like deletingProject which contain the ID or the name of the deleting project.
Then you just have to modify the loading icon span like this :
<span className={`${project.project_id === deletingProject ? "" : "hidden"}`}>

I would suggest creating a component to hold each project, and add the delete functionality to the individual component. For example:
Project.js
export default function Project({ project, index }) {
const [deleteLoading, setDeleteLoading] = useState(false);
return (
<li key={index}>
<NavLink to={`someurl`}>{project.project_name}</NavLink>
<span className={`${deleteLoading ? "" : "hidden"}`}>
<svg> ..loading icon.. </svg>
</span>
<span onClick={() => deleteProject(project._id)} className={`${deleteLoading ? "hidden" : ""}`}>
<svg> ..trash icon.. </svg>
</span>
</li>
}
}
And then import into the Parent component
import Project from './Project.js'
export default function Parent(props) {
return (
<ul className="mt-2">
{projects && projects.map((project, index) => (
<Project project={project} index={index}/>
)}
</ul>
)
)}
I hope that helps

If you want to keep each project logic and state separate I'd suggest to create another component and host any project related logic there.
An example
function ProjectItem({ name }) {
const [deleteLoading, setDeleteLoading] = useState(false)
function handleDelete() {
// Some logic here
setDeleteLoading(true)
}
return (
<li>
<NavLink to={`someurl`}>{name}</NavLink>
<span className={`${deleteLoading ? "" : "hidden"}`}>
<svg> ..loading icon.. </svg>
</span>
<span
onClick={handleDelete}
className={`${deleteLoading ? "hidden" : ""}`}
>
<svg> ..trash icon.. </svg>
</span>
</li>
)
}
function MainComponent() {
return (
<ul className="mt-2">
{projects &&
projects.map((project) => (
<ProjectItem key={project._id} name={project.project_name} />
))}
</ul>
)
}
NOTES: Never use index as key prop in React Components. If you have an id already then use that as it's key value. Key prop is intended to be unique among all your rendered components
If you have items that can have it's own state move them to a new component. Hope you end up liking React and learning a lot my man ;3

Related

React.js close all the previous containers at the same time

I got the container with children coming from props.[Container][1]
[1]: https://i.stack.imgur.com/3Y7Qm.png . When i click the arrow button it shows the content of the container. [Content][1]
[1]: https://i.stack.imgur.com/A8eZH.png . When i open the content container i want other containers to close . For now i can only close them with clicking the arrow button again.[Open Content][1]
[1]: https://i.stack.imgur.com/REh57.png .Here is my code `
import { useState } from "react";
export default function Question(props) {
const [clicked, setClicked] = useState(false);
function clickedElement() {
return setClicked(!clicked);
}
return (
<div className="question-cont">
<div className="question-cont-inner">
<h3>{props.head}</h3>
<button onClick={() => clickedElement()}>
{clicked ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
</div>
{clicked ? <p>{props.description}</p> : ""}
</div>
);
}
Here is the my parent component
import Question from "../components/Question";
import questions from "../components/Questions";
export default function Sorular() {
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
/>
);
});
return (
<div className="sorular-container">
<div className="sorular-top">
<div className="sorular-top-back-img">
<a href="/">
<img
src="./images/right-arrow-colorful.png"
id="right-arrow-img"
/>
</a>
</div>
<div className="sorular-top-head">
<img src="./images/conversation.png" />
<h4>Sıkça Sorulan Sorular</h4>
</div>
</div>
<div className="sorular-bottom">{questionList}</div>
</div>
);
}
`
You need to remove your const [clicked, setClicked] = useState(false); state variable from the component itself and move it into parent:
In parent add this at the beggining and modify questionList:
const [clickedElementId, setClickedElementId] = useState(null);
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
isOpened={question.id === clickedElementId}
onClickedElement={() => setClickedElementId(
question.id === clickedElementId ? null : question.id
)}
/>
);
});
And in the Question.jsx, swap button for the following:
<button onClick={() => props.onClickedElement()}>
{props.isOpened ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
// and later:
{props.isOpened ? <p>{props.description}</p> : ""}
This works by your app holding id of only one, currently open question, and swap it based on clicked element.
Note that questionId should be unique amongst all Question components, but you probably use .map to render them so you should use the same variable as you are passing into Question's key prop while rendering.

Unique "key" prop not working for my array mapping - REACT

My unique key is not working for my array mapping.
The Link component is from react-scroll. I've therefore wrapped it into a tag, and setting the key here (thinking this could be where the issue is from)
I understand using index for setting a unique key is bad practice. But I am really just trying to remove the warning message a this point!
Any help would be very much appreciated.
My code:
import styles from '../styles/NavImage.module.css'
import { Link } from 'react-scroll'
const NAVLINKSHOVER = ['About', 'Club', 'Team', 'Timing', 'FAQ']
const NavImage = ({toggleFaq, toggleState, hoveredIndex, setHoveredIndex}) => {
const handleClick = () => {
toggleFaq();
};
return (
<nav className={styles.navbar}>
<div className={styles.linkContainer}>
{
NAVLINKSHOVER.map ((navLink, index) => {
if (navLink === 'FAQ') {
return (
toggleState?(
<div key={index}>
<Link
to="main"
smooth={true}
offset={0}
duration={0}
onClick={handleClick}
className={`${styles.navLinks} ${styles.linkNotActive}`}
>FAQ
</Link>
</div>
):(
<div key={index.toString()}>
<Link
to="faq"
smooth={true}
offset={500}
duration={200}
onClick={handleClick}
className={`${styles.navLinks} ${styles.linkNotActive}`}
>FAQ
</Link>
</div>
)
)
}
return (
<>
<a
className={`${styles.navLinks} ${hoveredIndex === index? styles.linkActive : styles.linkNotActive}`}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(-1)}
key={index.toString()}
>
{navLink}
</a>
{
index!==NAVLINKSHOVER.length-1? <p className={styles.divider}> | </p> : null
}
</>
)
})
}
</div>
</nav>
);
}
export default NavImage;
You're returning a key in the case of FAQ, but not in any other cases; see here?
return (
<> <---- no key
<a
The top component returned needs a key. The string being iterated over would serve nicely without using the index.
{
NAVLINKSHOVER.map ((navLink, index) => {
if (navLink === 'FAQ') {
return (
toggleState?(
<div key="faq-main">
<Link
to="main"
smooth={true}
offset={0}
duration={0}
onClick={handleClick}
className={`${styles.navLinks} ${styles.linkNotActive}`}
>FAQ
</Link>
</div>
):(
<div key="faq">
<Link
to="faq"
smooth={true}
offset={500}
duration={200}
onClick={handleClick}
className={`${styles.navLinks} ${styles.linkNotActive}`}
>FAQ
</Link>
</div>
)
)
}
return (
<React.Fragment key={navLink}>
<a
className={`${styles.navLinks} ${hoveredIndex === index? styles.linkActive : styles.linkNotActive}`}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(-1)}
key={index.toString()}
>
{navLink}
</a>
{
index!==NAVLINKSHOVER.length-1? <p className={styles.divider}> | </p> : null
}
</React.Fragment>
)
})
}
You should wrap the bottom return statement in a div and then give that div a key, if you want to get rid of the warning.
<div key={index.toString()}>
<a
className={`${styles.navLinks} ${
hoveredIndex === index ? styles.linkActive : styles.linkNotActive
}`}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(-1)}
>
{navLink}
</a>
{index !== NAVLINKSHOVER.length - 1 ? (
<p className={styles.divider}> | </p>
) : null}
</div>
Pls note it is still a bad practice to use index as a key.

How can I set an element id to active when another component is scrolled into view?

So I have a sidebar and inside of it multiple buttons. When I click on the particular button, it scrolls into view a component with a certain name(I have one page with multiple components). And it works fine, components scroll into view,
but I want to set a list item id to active, according to the current component in view, so it changes color, but in the other li items active class is removed.
SideBar.jsx:
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<img src={spike} />
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className="row"
id={val.link == val.title ? "active" : ""}
key={key}
onClick={() => {
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
</div>
);
};
So as you can see, I have a ul with list items, and when I click on each one, it scrolls a certain div into view. I also SidebarData.js file, where I store all data as an array:
SidebarData.js
export const SlidebarData = [
{
title: "Home",
link: "home"
},
{
title: "About",
link: "about"
},
{
title: "Services",
link: "services"
},
{
title: "Contact",
link: "contact"
}
];
So when a particular div is in view, I want to set a li id to active, but I can't figure out how I can tell li to do it.
you're changing id instead of class on li and id can’t be duplicate, it is not assigned correctly.
Instead of using id in your code, you might use ref.
here is a sample code to add active class to li based on elements in view.
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [selectedLink, setSelectedLink] = useState("");
return (
<div className="sidebar">
<span className="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<img src={spike} />
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className={`row ${selectedLink === val.link ? "active" : ""}`}
id={val.link}
key={key}
onClick={() => {
setSelectedLink(val.link);
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
</div>
);
};
There are two problems with your solution. One is that your SlidebarData has different titles and links. Thus, when using val.link === val.title in Sidebar hook, you're getting false on the condition, returning the id as blank.
On the other hand, I'm not sure how you're not getting an error with document.getElementById(val.link).scrollIntoView();. The value of val.link is going to be either Home, About, ...; however, you're setting the id of each li as either "active" or blank (""). So, document.getElementById(val.link) should return null and not the element.
EDIT: Does this solve your problem?
If you want to set the id which is active when pressed, do not use the keyword active as you'll have to change all the other id's. Create a state variable currentId (for example), and use it to set the current item you've selected.
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [currentId, setCurrentId] = useState("");
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className="row"
id={val.link}
key={key}
onClick={() => {
setCurrentId(val.link);
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
<div>{currentId}</div>
</div>
);
}
Edit #2: Here is the codesandbox link, so you can have a look at the behaviour of the aforestated code.
https://codesandbox.io/s/fluent-ui-example-forked-rbd9p?file=/index.js
You'll need to use the Intersection Observer API, which will allow you to monitor and react to events which occur when tracked elements intersect a parent element (or the viewport).
Implementing this in React is non-trivial and will likely involve forwarding refs for each tracked element, however, you might find one or more community modules which already exist (e.g. react-intersection-observer).

Using document.classlist.remove in react

When I click on a new li tag, I want the classname to change to active (which works), but also remove active from the rest of the li tags (which doesn't work). document.getElementByTagName('li').classList.remove="active", doesn't work because it is saying it is not defined. Should I go about this a different way... maybe storing something different in the state?
import React, {useState, useEffect} from 'react';
import './Anime.css';
function Anime(){
const [currentCase, setCurrentCase] = useState(0)
function getAnime(){
fetch('https://kitsu.io/api/edge/anime')
.then(response => response.json())
.then(data => console.log(data));
}
function currentSelector(e){
document.getElementsByTagName('li').clasList.remove("active");
setCurrentCase(e.target.value)
e.target.className = "active"
}
useEffect(() => {
getAnime();
}, []);
return(
<div className="anime">
{/* Selectors */}
<ul>
<li value= {0} className="active" onClick={currentSelector}>Trending</li>
<li value={1} onClick={currentSelector}>Action</li>
<li value={2} onClick={currentSelector}>Adventure</li>
<li value={3} onClick={currentSelector}>Comedy</li>
<li value={4} onClick={currentSelector}>Drama</li>
<li value={5} onClick={currentSelector}>Magic</li>
<li value={6} onClick={currentSelector}>Romance</li>
</ul>
</div>
)
}
export default Anime
Don't use the usual DOM API for things like this in React, instead use React's state management and conditional rendering functionality. You already have a state variable to track the active case (currentCase), so you can just set the class name conditionally while rendering.
For each li, just check if the value of currentCase matches the value for that li and if so, give that li the class active, otherwise give a different class.
For example:
import React, {useState, useEffect} from 'react';
import './Anime.css';
function Anime(){
const [currentCase, setCurrentCase] = useState(0)
function getAnime(){
fetch('https://kitsu.io/api/edge/anime')
.then(response => response.json())
.then(data => console.log(data));
}
function currentSelector(e){
setCurrentCase(Number(e.target.value));
}
useEffect(() => {
getAnime();
}, []);
return(
<div className="anime">
{/* Selectors */}
<ul>
<li value={0} className={currentCase === 0 ? "active" : ""} onClick={currentSelector}>
Trending
</li>
<li value={1} className={currentCase === 1 ? "active" : ""} onClick={currentSelector}>
Action
</li>
<li value={2} className={currentCase === 2 ? "active" : ""} onClick={currentSelector}>
Adventure
</li>
<li value={3} className={currentCase === 3 ? "active" : ""} onClick={currentSelector}>
Comedy
</li>
<li value={4} className={currentCase === 4 ? "active" : ""} onClick={currentSelector}>
Drama
</li>
<li value={5} className={currentCase === 5 ? "active" : ""} onClick={currentSelector}>
Magic
</li>
<li value={6} className={currentCase === 6 ? "active" : ""} onClick={currentSelector}>
Romance
</li>
</ul>
</div>
)
}
export default Anime
Or extract the class name logic into a function (defined within your Anime component) and call that function for each li element:
function getLiClassName(value) {
if (value === currentCase) {
return "active";
}
return "";
}
And use like this:
<li value={0} className={getLiClassName(0)} onClick={currentSelector}>
Trending
</li>

How to pass value to component which I get from another by onClick event

I'm a newbie in react. So sorry)
I created my icon component which has svg icons inside:
<IconPick icon={'globe'} />
then I created array of icons' names:
iconsList = ['globe', 'mail', ...];
And map through this component to show all icons that I have in one block:
<ul className="icons-list__wrapper">
{iconsList.map(icon =>
<li data-icon={icon} key={icon}>
<IconPick icon={icon} />
</li>
)}
</ul>
Everything is working fine. Now I want to make from this block kind of Icon Picker.
So when person click on any icon it will be appeared in a new block, so I used onClick:
handleCheck = (e) => {
e.currentTarget.dataset.icon
}
...
<div>
<ul className="icons-list__wrapper">
{iconsList.map(icon =>
<li
data-icon={icon}
key={icon}
onClick={this.handleCheck}
>
<IconPick icon={icon} />
</li>
)}
</ul>
</div>
...
So now I create a new div in which I want to pass data value that I get onClick into clickedIcon:
<div>
<IconPick icon={clickedIcon}/>
</div>
But I can't do it right, can you help me? Or just advice.
I will be very grateful.
You can define the clickedIcon as state of the component.
state={
clickedIcon:undefined // initially it is undefined
}
In your handler you update the clickedIcon value :
handleCheck = icon => this.setState({clickedIcon : icon});
Pass the icon clicked value :
<li
data-icon={icon}
key={icon}
onClick={()=>this.handleCheck(icon)}>
<IconPick icon={icon} />
</li>
And finally you can pass the clickedIcon value to your IconPick easily :
<div>
<IconPick icon={this.state.clickedIcon}/>
</div>
One of the benefits of React is that it lets you handle your data in a meta state on your Components instead of making you insert the same data as data-attributes to be later collected by your 'handling code'.
Try using:
class IconPicker extends Component {
constructor(props){
super(props)
this.state = {
iconList: [ '...'],
selectedIcon: ''
}
}
render() {
return (
<div>
<ul className="icons-list__wrapper">
{this.state.iconsList.map(icon =>
<li
key={icon}
onClick={() => this.setState({...this.state, selectedIcon: icon})}
>
<IconPick icon={icon} />
</li>
)}
</ul>
<div>
<IconPick icon={this.state.selectedIcon}/>
</div>
</div>
);
}
}

Resources