An element is not removed from the array, how to fix it? - reactjs

I have data that I get from api and through the map() method I display these "cards", each card has an image when clicked on which this image should receive an additional class. I implemented this by adding the index of the card to an array and now I can assign new classes to them, but I can't delete them
P.S. I have strict mode enabled, if it is disabled it removes extra classes on all other cards except the one I clicked on
//On the first click, it adds to the array, on the second click, it must delete it (index is written to the array)
function toggleFavoriteChanel(index) {
setFavorite(prevState => {
let returnArray = prevState;
if(prevState.includes(index)){
console.log(prevState)
console.log(index)
return returnArray.splice(prevState.indexOf(index), 1)
}else{
// here are 3 dots
return [..returnArray, index]
}
})
}
// <img src={star} alt="star".../>
{Array.isArray(props.visibleData) ? props.visibleData.map((chanel, index) => {
return (
<>
<div className="chanel__item" key={index}>
<img src={star} alt="star" onClick={() => props.toggleFavoriteChanel(index)} id={index} className={`star ${props.favorite.includes(index) ? 'active' : ''}`} />
<NavLink
onClick={() => props.updateData(index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{index + 1}. </div>{chanel.name_ru}</div>
</NavLink>
</div>
</>
)
}) : null}

The issue is that you are setting favorite to the return value of splice, which is an array containing the deleted elements (from MDN docs on splice). What you want instead is to return returnArray after calling splice on it.
Just change this line in toggleFavoriteChanel:
return returnArray.splice(prevState.indexOf(index), 1)
to:
returnArray.splice(prevState.indexOf(index), 1);
return returnArray;
While the above should fix your issue, I would recommend approaching this problem in a different way if you are just trying to toggle a CSS class in response to clicking (assuming you don't need a list of the favorited cards at a higher level).
The approach is to define a component for the card and hold the isFavorite (clicked) state locally rather than in an array in an ancestral component.
Here's a rough example:
function Card(props) {
const [isFavorite, setIsFavorite] = React.useState(false);
return (
<div className="chanel__item">
<img
src={star}
alt="star"
onClick={() => setIsFavorite(prev => !prev)}
id={props.index}
className={`star ${isFavorite ? 'active' : ''}`}
/>
<NavLink
onClick={() => props.updateData(props.index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{props.index + 1}. </div>{props.chanel.name_ru}</div>
</NavLink>
</div>
)
}

Related

How do I access specific items in a mapped list through an onClick?

So I'm losing my mind over here trying to essentially just select the sibling element of things that are mapped.
I basically map over an object from an API, grab some stuff from it, and display some text and an ul with some li in it.
I ultimately want to click on h2 and have it alter the ul below it. In this case, I want the ul item below it to have a display of none until clicked.
I also tried returning the JSX of the ul in the onClick of the h2 tag, but it didn't add any elements to the page.
This wouldn't be so bad if I knew what anything was going to be, and it was a set object, but being an API, I could have 1 or 1000 ul to work with that all need the same functionality.
I was reading to do it by state, but I don't know how many items there are going to be, so how would I set state on an undefined number of items? Also, wouldn't having 100 states for this be crazy if that's what my API returned??
return myData.map((obj) => (
<div style={{ maxWidth: "500px", marginLeft: "25%" }} key={obj.name}>
<img
src={obj.image}
width="500px"
></img>
<h1>{obj.name}</h1>
<h2 id={'ingredients'+Math.Random} onClick={(e) => {console.log(e)}}>Ingredients</h2>
<ul id={'UL'+Math.random()}>
{obj.ingredients.map((index) => (
<ListItem key={"ingredients" + Math.random()} value={index} />
))}
</ul>
Side note - this code is ultimately for my practice only, so ignore bad naming etc...
You can achieve this by only using one state. Assuming your obj.name is unique since you are using it as a key value. You can use this name to determine wheter or not to display your ul, if the id is in the array then display the ul, if not do not display.
const YourComponent = () => {
// state to track all the ids of all the expanded ul's
const [expandedIds, setExpandedIds] = useState([]);
const toggleVisibility = (idToToggle) => {
// set new state
setExpandedIds((prevIds) => {
// if the id to toggle is in the array, if not add it
if (!expandedIds.includes(idToToggle)) return [...prevIds, idToToggle];
// else remove the id
return prevIds.filter((id) => id !== idToToggle);
});
};
return myData.map((obj) => (
<div style={{ maxWidth: "500px", marginLeft: "25%" }} key={obj.name}>
<img src={obj.image} width="500px"></img>
<h1>{obj.name}</h1>
<h2 onClick={() => toggleVisibility(obj.name)}>Ingredients</h2>
{expandedIds.includes(obj.name) && (
<ul id={"UL" + Math.random()}>
{obj.ingredients.map((index) => (
<ListItem key={"ingredients" + Math.random()} value={index} />
))}
</ul>
)}
</div>
));
};
I hope this helps you with your project.

React.js : index.js:1 Warning: Each child in a list should have a unique "key" prop

I'm doing an app, in the fronted i'm using react, and whenever that i use the map function, i get these errors.
And you would think... Well, that's because you didn't assing any key to your container, and the truth is that, i did... but the errores keeps showing.
Let me show you the code
{specificPhotos.map((photo, i) => {
return (
<>
{/* The Photo */}
<PhotoWrapper key={photo.id}>
{/* For Images */}
{photo.photo.charAt(5) === "i" && (
<img src={photo.photo} />
)}
{/* For Videos */}
{photo.photo.charAt(5) === "v" && (
<>
<video src={photo.photo} />
</>
)}
{/* For Videos Too */}
{photo.photo.charAt(5) === "v" && (
<>
<VideoPlay>
<VideoPlayIcon />
</VideoPlay>
</>
)}
And the other time i used map fn as well
{singlePhoto.comments.map(comment => {
return (
<>
<ContainComment key={comment.id}>
{/* Photo */}
<PresentationImg
style={{ width: "100%", maxHeight: "50px" }}
area="perfil"
>
<CommentPresentation>
<img src={comment.presentation} />
</CommentPresentation>
</PresentationImg>
{/* Body */}
<TheComment>
<h2>
{comment.user} <span>{comment.body}</span>
</h2>
</TheComment>
So, where am i supossed to do with the key? maybe i did something wrong... I don't know, just help me and thanks for your time !
The problem is your second code snippet.
Option 1:
If you can remove the React fragment i.e <></>
Option 2:
However, If the React fragment is serving the purpose of wrapping the elements together, you can replace it with a
<div key={comment.id}><div/>
this means that you can remove the key={comment.id} on the <ContainComment key={comment.id}>
Your key should be on the top most element, which in this case are the fragments. See keyed-fragments
<React.Fragment key={comment.id}>
<ContainComment>
you need to add a key to the fragment right after the return (<>), but unfortunately you can only add a key if it is imported as Fragment (<Fragment key={}></Fragment>), instead of using the short syntax.
As mentioned above, the React fragment <></> is the issue.
Why is this:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
different from this?
const todoItems = todos.map((todo) =>
<>
<li key={todo.id}>
{todo.text}
</li>
</>
);
Because the fragment needs the key on it (or removed) in the 2nd snippet to prevent the map error.
If you need to keep the fragment to avoid adding an unnecessary div, you could also do:
const todoItems = todos.map((todo) =>
<React.Fragment key={todo.id}>
<li>
{todo.text}
</li>
</React.Fragment>
);
Source: https://reactjs.org/docs/lists-and-keys.html#keys

Anchor tag '<a>' not working on first click inside Slick

I am placing an anchor tag inside slick slider (React-Slick), it is not working on the first click, but on the second click onwards , this is for each image.
Code :
<MediaQuery query={`(max-device-width: ${belowIpadBreakPoint}px)`}>
<Slider className="marketing-carousel__body" {...settings}>
{objectPath.has(this.state, 'config.content.images') &&
this.state.config.content.images.map((item, index) => {
return (
<div key={index.toString()} className="marketing-carousel__body_image">
{ this.state.config.content.ctaLink ?
<a href={this.state.config.content.ctaLink} target={this.state.config.content.ctaTarget} id="marketing-carousel-mobile-link">
<Picture
sources={item.sources}
defaultSrc={item.defaultSrc}
isLazyLoad={item.isLazyLoad}
altText={item.altText}
customClass={item.customClass}
/>
</a>
:
<Picture
sources={item.sources}
defaultSrc={item.defaultSrc}
isLazyLoad={item.isLazyLoad}
altText={item.altText}
customClass={item.customClass}
/>
}
</div>
);
})}
</Slider>
So I fixed it on my own.
This is what I did:
<a href={this.state.config.content.ctaLink} target={this.state.config.content.ctaTarget} onClick={(e) => this.openLinks(e)}>
I put an onClick listener and created a function. This seemed to have solved the issue, it is an existing issue with slick.js.
openLinks = (event) => {
event.preventDefault();
if (event.currentTarget.target === '_blank') {
window.open(event.currentTarget.href, event.currentTarget.target);
} else {
window.location = event.currentTarget.href;
}
};

React toggle button based on API return

I am currently working on a React application.
As you can see in the following code, there are two buttons. I would like to show the grey button if the user hasn't created a Request (default). If the user has created a Request the button should be green.
Problem:
The main issue is, that the map function (API) no returning any value for "no Requests" so I am not able to identify "no Requests". That means that .isEmpty,.length, .indexOf,... and also the "if-else" is not working because there is nothing to validate.
const greenButton = (
<Button color="green" onClick={e => DeleteRequest(e, rest.id)}>Request</Button>
);
const greyButton = (
<Button color="grey" onClick={e => CreateRequest(e, props.reservationID)}>Request</Button>
);
return (
<div>
{greyButton}
{requests.map((rest, i) => (
<div key={i}>
{rest.requester === username
? <div>{greenButton}{rest.requester} <i
aria-hidden="true"
className="delete icon"
onClick={e => DeleteRequest(e, rest.id)} />
</div> : <div />}
</div>
))}
</div>
);
Result in the UI:
API:
Any Ideas? (If you need more information, I am happy to provide more details)
conditional rendering
Use a ternary on requests being a defined/truthy object and has a truthy length property. In the true branch map the requests, grey button in false branch. This covers requests being either initially (or returned from the API) undefined or an empty array [].
return (
<div>
{requests && requests.length ? requests.map((rest, i) => (
<div key={i}>
{rest.requester === username
? <div>{greenButton}{rest.requester} <i
aria-hidden="true"
className="delete icon"
onClick={e => DeleteRequest(e, rest.id)} />
</div> : <div />}
</div>
)) : (
{greyButton}
)}
</div>
);

How to avoid React Hook UseState to share the states?

I may have a bad title for this question, but here's my situation.
I use a chunk of json to render a list. The list item can be expanded and showed the sub list if it has children property. The json structure includes two arrays and each array contains more sub-arrays. I use tabs to switch arrays.
I use useState to manage the value isExpanded of each individual sub-array component. but it seems like the state isExpaned is shared for all tabs.
The state isExpanded remains same even if I switch to another tab. In other words, why the sub-list keep expanded when I switch to another tab?
In addition, why the expanded sub-list of each tab overlaps each other. They should keep 'close' when I switch to another tab because I set the initial state to false already. (const [isExpand, setIsExpand] = useState(false))
const ListItem = ({name, children}) => {
const [subList, setSubList] = useState(null)
const [isExpand, setIsExpand] = useState(false)
const handleItemClick = () => {
children && setIsExpand(!isExpand)
console.log(isExpand)
}
useEffect(() => {
isExpand && children && setSubList(children)
}, [isExpand, children])
return (
<div className='list-wrapper'>
<div className='list-item'>
{name}
{
children &&
<span
className='expand'
onClick={() => handleItemClick()}>
{isExpand ? '-' : '+'}
</span>
}
</div>
<div className='list-children'>
{
isExpand && subList && subList.map((item, index) =>
<ListItem key={index} name={item} />
)
}
</div>
</div>
)
}
Here's the codesanbox, anyone helps?
It seems like React is confused due to index being used as ListeItem key.
(React will try to "share" isExpanded state as they look the same according to the key you specified)
You could change the key from key={index}
<div className="contents">
{contents &&
contents.children &&
contents.children.map((item, index) => (
<ListItem
...... 👇 ....
key={index}
name={item.name}
children={item.children}
/>
))}
</div>
to use more distinct key, item.name
<div className="contents">
{contents &&
contents.children &&
contents.children.map(item => (
<ListItem
...... 👇 ....
key={item.name}
name={item.name}
children={item.children}
/>
))}
</div>
Check out the forked sandbox.
https://codesandbox.io/s/soanswer57212032-9ggzj

Resources