Issue with ReactJS Keys - reactjs

I have some JSX that is giving me the error:
Warning: Each child in a list should have a unique "key" prop
which I've run across before, but this time I can't see where the problem is. The markup is as follows:
return isLoading ? (
<div className="App">
<div className="spinner"></div>
<div className="App loading">
<p><i>loading...</i></p>
</div>
</div>
)
: hasError ? (
<div className="App loading-error">
⚠ There is a network issue: Please try again later
</div>
)
:
(
<div className="App" key={`op - ${Math.floor(Math.random() * 110)}`}>
<>
{shelves.map(s => {
return (
books[s].map((b,i) => {
return (
<>
<h2>{((shelvesN.filter(o => o.value === s)))[0].label}</h2>
<div key={`${s}-${i}`}>
<Link to={`/book/${b.id}`}>
<img src={b.imageLinks.thumbnail} alt={b.title}></img>
</Link>
<Select className="sel-x"
placeholder="Choose a bookshelf..."
value={""}
options={shelvesN}
onChange={opt => changeShelf(opt.value,b.id)}
/>
</div>
</>
)
})
)
})}
</>
</div>
)
I'd appreciate any help. Thanks.
k.

So, the way i see it, you are stuck here:
<div className="App" key={`op - ${Math.floor(Math.random() * 110)}`}>
<>
{shelves.map(s => {
return (
books[s].map((b,i) => {
return (
<>
<h2>{((shelvesN.filter(o => o.value === s)))[0].label}</h2>
<div key={`${s}-${i}`}>
<Link to={`/book/${b.id}`}>
<img src={b.imageLinks.thumbnail} alt={b.title}></img>
</Link>
<Select className="sel-x"
placeholder="Choose a bookshelf..."
value={""}
options={shelvesN}
onChange={opt => changeShelf(opt.value,b.id)}
/>
</div>
</>
)
})
)
})
this block contains 2 maps, which returns lists.
the inner one shouldn't use <>, because it needs the key reference, so should the first one. something like:
shelves.map((s, I) => {
return (
<div key={I}>
{books[s].map((b,i) => {
return (
<div key={`${I}-${i}`}>
<h2>{((shelvesN.filter(o => o.value === s)))[0].label}</h2>
<div >
<Link to={`/book/${b.id}`}>
<img src={b.imageLinks.thumbnail} alt={b.title}></img>
</Link>
<Select className="sel-x"
placeholder="Choose a bookshelf..."
value={""}
options={shelvesN}
onChange={opt => changeShelf(opt.value,b.id)}
/>
</div>
</div>
</>
)
})}
</div>)
}

You are returning React.Fragment from each array item, so assign the unique key to each Fragment, here:
books[s].map((b,i) => { return ( <> ..... </> )})
Use this code:
<div className="App" key={`op - ${Math.floor(Math.random() * 110)}`}>
<>
{shelves.map(s => {
return (
books[s].map((b,i) => {
return (
// ↓↓↓↓↓↓↓↓ add key here
<React.Fragment key={i}>
<h2>{((shelvesN.filter(o => o.value === s)))[0].label}</h2>
<div>
<Link to={`/book/${b.id}`}>
<img src={b.imageLinks.thumbnail} alt={b.title}></img>
</Link>
<Select className="sel-x"
placeholder="Choose a bookshelf..."
value={""}
options={shelvesN}
onChange={opt => changeShelf(opt.value,b.id)}
/>
</div>
</React.Fragment>
)
})
)
})}
</>
</div>

You use iteration, which means you must use keys. topic
Your code looks awful and unreadable, you should work better on the style.
shelves.map((s, I) => {
return (
<div key={I}>
{books[s].map((b,i) => {
return (
<div key={`${I}-${i}`}>
<h2>{((shelvesN.filter(o => o.value === s)))[0].label}</h2>
<div >
<Link to={`/book/${b.id}`}>
<img src={b.imageLinks.thumbnail} alt={b.title}></img>
</Link>
<Select className="sel-x"
placeholder="Choose a bookshelf..."
value={""}
options={shelvesN}
onChange={opt => changeShelf(opt.value,b.id)}
/>
</div>
</div>
</>
)
})}
</div>)
}

Related

is there a way to display each individual comment data without it being set to the recently clicked comment?

im currently building a website similar to reddit but im having troubles with the comments, i can toggle the comment on and off on click but whenever more than one comment is clicked it just displays the comment data of the recent comment that was clicked.
this is my code:
const handleComments = (subText, id) => {
setCommentIds(prev => (prev.includes(id) ? prev.filter(cid => cid !== id) : [...prev, id]));
dispatch(getComments({ subText, id }));
};
const popularRendering = () => {
if (popular.isLoading) {
return Array(5)
.fill()
.map((_, index) => <FeedSkeleton key={index} />);
} else if (popular.data && popular.data.data) {
return popular.data.data.children.map((data, index) => (
<div className="box-container" key={index}>
<div className="data-container">
<div className="votes-container">
<TiArrowUpOutline
size={27}
className={`${upvoted[data.data.id] ? "up-voted" : "up-vote"}`}
onClick={() => handleUpVote(data.data.id)}
/>
<p className={upvoted[data.data.id] ? "up-voted" : downvoted[data.data.id] ? "down-voted" : ""}>{formatNumber(data.data.score)}</p>
<TiArrowDownOutline
size={27}
className={`${downvoted[data.data.id] ? "down-voted" : "down-vote"}`}
onClick={() => handleDownVote(data.data.id)}
/>
</div>
<div className="feed-header">
{subredditIconUrl[data.data.subreddit_name_prefixed] ? (
<img className="feed-icon-img" src={subredditIconUrl[data.data.subreddit_name_prefixed]} alt="subreddit-icon" />
) : null}
<p>{data.data.subreddit_name_prefixed}</p>
<span>Posted by u/{data.data.author} </span>
<span>{formatDate(data.data.created_utc)}</span>
</div>
<div className="feed-body">
<p>{data.data.title}</p>
{isImage(data.data.url) ? (
<img src={data.data.url} alt="subreddit" />
) : data.data.is_video ? (
<video height="auto" width="100%" controls>
<source src={data.data.media.reddit_video.fallback_url} />
<p className="error-media">Sorry, this content cannot be displayed.</p>
</video>
) : null}
</div>
<div className="footer">
<div className="comments" onClick={() => handleComments(data.data.subreddit_name_prefixed, data.data.id)}>
<GoComment size={25} className="comment-icon" />
<p>{formatNumber(data.data.num_comments)} Comments</p>
</div>
</div>
{subreddit.commentsLoading
? Array(5)
.fill()
.map((_, index) => <CommentsSkeleton key={index} />)
: commentIds.includes(data.data.id) && <Comments postId={data.data.id} />}
</div>
</div>
));
}
};
i tried using an array to store the ids and only display the ids of those comments but it just always resulted in issues and an infinite loop of headaches and errors.

TypeScript how to write this more pragmatic?

Can I write this code more pragmatic somehow (mainly the RenderMenu I want to improve, but suggestions on both is appreciated)? I can't really think of how. I'm not used to TypeScript. I'm rendering a food-menu from JSON (import {data} from './DinnerData'. First I render the title e.g. "Starters" then it goes through all the starters and rendering each dinner with two map functions.
export const RenderMenu = (props: Props) => {
return (
<>
{data.types.map((type, index) => {
return (
<>
<div className="row">
<div className="col-xl-12">
<div className="section-title text-center">
<h4 key={index}>
{type.name}
</h4>
</div>
</div>
</div>
<div className="row menu_style1">
{type.items.map((item, index) => {
return (
<>
<Dinner title={item.name} price={item.price} children={item.text} image={"https://i.imgur.com/kbpceNv.jpg"} />
</>
)
})}
</div>
</>
)
})}
</>
);
};
Here is the Menu itself.
export const Menu = (props: Props) => {
return (
<>
<section className="about-area pt-60 m-2 p-2" id="home">
<div className="container mb-5">
<div className="row">
<div className="col-xl-12 mb-60">
<div className="section-title text-center">
<p></p>
<h1>Vår meny</h1>
</div>
</div>
</div>
<RenderMenu/>
</div>
</section>
</>
);
};
You can extract smaller components.
MenuTitle
export const MenuTitle = (props) => {
const { name, key } = props;
return (
<div className="row">
<div className="col-xl-12">
<div className="section-title text-center">
<h4 key={key}>{name}</h4>
</div>
</div>
</div>
);
};
MenuItems
export const MenuItems = (props) => {
const { items } = props;
return (
<div className="row menu_style1">
{items.map((item, index) => {
<>
<Dinner
title={item.name}
price={item.price}
children={item.text}
image={"https://i.imgur.com/kbpceNv.jpg"}
/>
</>;
})}
</div>
);
};
RenderMenu is now more cleaner & easier on the head.
export const RenderMenu = (props) => {
return (
<>
{data.types.map((type, index) => {
return (
<>
<MenuTitle name={type.name} key={index} />
<MenuItems items={type.items} />
</>
);
})}
</>
);
};

React.js: how to sort() and map() or only map() conditionally in JSX

I have this code below which doesn't seem to be effective as I use almost the same code twice. I would like to make this code much cleaner than this by not just pasting and copying.
<div >
{ selected ? (
dynamicData
.sort((a, b) => b.count_filtered - a.count_filtered) // Only this part is different
.map((list, idx) => (
<div key={list.slug}>
<label htmlFor={list.slug}>
{list.name} (<b> {list.count_filtered} </b> / {list.count_all})
</label>
<input
type="checkbox"
value={list.slug}
id={list.slug}
checked={filterList.some(el => el.slug.includes(list.slug))}
/>
</div>
))
) : (
dynamicData.map((list, idx) => (
<div key={list.slug}>
<label htmlFor={list.slug}>
{list.name} (<b> {list.count_filtered} </b> / {list.count_all})
</label>
<input
type="checkbox"
value={list.slug}
id={list.slug}
checked={filterList.some(el => el.slug.includes(list.slug))}
/>
</div>
))
)}
</div>
As you can see, if selected is true, the array is going to sort() and map() otherwise, it will only do map().
Please let me know the clever way to clean this code.
According to what #MuhammedJaseem said, I updated my code below, and it works well.
const repeatCode = (list, onChange) => { // Added
return (
<div key={list.slug}>
<label htmlFor={list.slug}>
{list.name} (<b> {list.count_filtered} </b> / {list.count_all})
</label>
<input
type="checkbox"
value={list.slug}
id={list.slug}
checked={filterList.some(el => el.slug.includes(list.slug))}
onChange={e => onChange(e.target.checked, list.slug, dynamicType)} // Added
/>
</div>
)}
<div >
{ selected ? (
dynamicData
.sort((a, b) => b.count_filtered - a.count_filtered)
.map((list, idx) => (
repeatCode(list, handleSelect)
))
) : (
dynamicData.map((list, idx) => (
repeatCode(list, handleSelect)
))
)}
</div>
Try this:
const repeatCode = (
<div key={list.slug}>
<label htmlFor={list.slug}>
{list.name} (<b> {list.count_filtered} </b> / {list.count_all})
</label>
<input
type="checkbox"
value={list.slug}
id={list.slug}
checked={filterList.some(el => el.slug.includes(list.slug))}
/>
</div>
)
<div >
{ selected ? (
dynamicData
.sort((a, b) => b.count_filtered - a.count_filtered) // Only this part is different
.map((list, idx) => (
{repeatCode}
))
) : (
dynamicData.map((list, idx) => (
{repeatCode}
))
)}
</div>

how to render a button for just the first element in the array in react

i want to render the download button just for the first element in the array and for the rest the button will be disabled
allFileVersions.map((eachVersion) => {
return (
<div className="d-flex">
<div className="">versions:</div>
<div className="">{eachVersion.FileName}</div>
{/* <div>DOwnload</div> */}
<div
className={
"saveblue mg-r-10 " +
(allFileVersions[0]
? ""
: "disabled")
}>
<i className="fas fa-download pd-r-5" /> Download</div>
</div>
);
})
allFileVersions.map((eachVersion, index) => {
return (
<div className="d-flex">
<div className="">versions:</div>
<div className="">{eachVersion.FileName}</div>
{/* <div>DOwnload</div> */}
<div className={"saveblue mg-r-10 " + (index === 0 ? "" : "disabled")}>
<i className="fas fa-download pd-r-5" /> Download</div>
</div>
);
})
allFileVersions.map((eachVersion,index) => {
return (
<div className="d-flex">
<div className="">versions:</div>
<div className="">{eachVersion.FileName}</div>
{index === 0 && (
<div>DOwnload</div>
)}
<div
className={
"saveblue mg-r-10 " +
(allFileVersions[0]
? ""
: "disabled")
}>
<i className="fas fa-download pd-r-5" /> Download</div>
</div>
);
})
The map function gives you two values. The current item of the iteration and the current index. You can use the index to check if it's the first item.
allFileVersions.map((eachVersion, index) => {
return (
versions:
{eachVersion.FileName}
{/* DOwnload */}
<div
className={
saveblue mg-r-10 ${index === 0 ? "" : "disabled"}
}>
Download
);
})
Use the index for your logic instead.

Why don´´t work onClick on option element

I'm developing a plugin to search streets with elastic search, ok?
I have a datalist to show my options to select.
When I receive info from database I create all html option elemtns and add click event to capture and handle.
But I dont know why not works onClick event that I've added to each option element.
Here is my code EDITED:
render() {
const { value, streets, error, labelError } = this.state;
return (
<div className="w-100 d-flex flex-column">
<div className="plug-search plug-search__content">
<div className="plug-inner-addon">
<input onKeyDown={this.handlePressEnter.bind(this)} onChange={this.handleSearch.bind(this)} type="text" placeholder={PLACEHOLDER} value={value} list="streets" autoComplete="on" />
<datalist id="streets">
{ streets && streets.length > 0 && streets.map((street, index) => {
return (
<Item street={street} position={index} key={index} />
);
})}
</datalist>
</div>
<div className="plug-btn-search plug-btn-search__content">
<i onClick={this.handleGetGeometry} className={`icon-search plug-search-icon`}></i>
</div>
</div>
{error &&
<div className={`plug-error plug-error__content ${(error) ? 'slideDown' : 'slideUp'}`}>
<label className="plug-label">{labelError}</label>
</div>
}
</div>
);
}
Now I've created a Item component, but still doesnt work:
class Item extends Component {
clickedOption = (event, index) => {
console.log('clicked');
console.log('value: ', event.target.value);
console.log('index: ', index);
};
render() {
return (
<div className="option" onClick={(event) => this.clickedOption(event, this.props.position)}>
<option value={this.props.street.nombre} />
</div>
)
}
}
export default Item;
Currently, <datalist /> don't support onClick events in his <options />, you could see this question, in order to apply an option in this kind of cases. Hope this help.
Thanks to Alberto Perez
Solved:
clickedOption = (event) => {
console.log('clicked');
console.log('value: ', event.target.value);
};
render() {
const { value, streets, error, labelError } = this.state;
return (
<div className="w-100 d-flex flex-column">
<div className="plug-search plug-search__content">
<div className="plug-inner-addon">
<input onInput={this.clickedOption} onChange={this.handleSearch.bind(this)} type="text" placeholder={PLACEHOLDER} value={value} list="streets" autoComplete="on" />
<datalist id="streets">
{ streets && streets.length > 0 && streets.map((street, index) => {
return (
<option value={street.nombre} key={index} />
);
})}
</datalist>
</div>
<div className="plug-btn-search plug-btn-search__content">
<i onClick={this.handleGetGeometry} className={`icon-search plug-search-icon`}></i>
</div>
</div>
{error &&
<div className={`plug-error plug-error__content ${(error) ? 'slideDown' : 'slideUp'}`}>
<label className="plug-label">{labelError}</label>
</div>
}
</div>
);
}
handleSearch = (e) => {
this.setState({
value: e.target.value
})
}
render() {
const { value, streets, error, labelError } = this.state;
return (
<div className="w-100 d-flex flex-column">
<div className="plug-search plug-search__content">
<div className="plug-inner-addon">
<input onKeyDown={this.handlePressEnter.bind(this)} onChange={this.handleSearch.bind(this)} type="text" placeholder={"placeholder"} value={value} list="streets" autoComplete="on" />
<datalist id="streets">
{ streets && streets.length > 0 && streets.map((street, index) => {
return (
<div key={index} className="option">
<option value={street.nombre} />
</div>
);
})}
</datalist>
</div>
<div className="plug-btn-search plug-btn-search__content">
<i onClick={this.handleGetGeometry} className={`icon-search plug-search-icon`}></i>
</div>
</div>
{error &&
<div className={`plug-error plug-error__content ${(error) ? 'slideDown' : 'slideUp' }`}>
<label className="plug-label">{labelError}</label>
</div>
}
</div>
);
}

Resources