I'm doing a library project (see the link for more explaination) with react
, redux-toolkit
and typescript
.
I'm doing an addToWishlist
funcionality: the user simply click on the star, the code dispatches the action which patch the database and the state is updated. There is another action to remove the book from the list.
In <Wishlist />
component I want of course render the titles and other data of each book. So I useAppSelector
to retrieve wishlist
and the entire list
of books, and with help of filter
and map
I would get the corresponding books, but I'm not able to render them, because it gives me a ts
error: index.d.ts(1373, 9): The expected type comes from property 'children' which is declared here on type 'DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement>'
Important note about the structure
wishlist
is an array of strings, which are the ids of the books.
The entire list
of books instead, is an array of objects: every object is a book, with title, author, id etc.
My questions:
What is going wrong here?
Basically, is it better to save in the wishlist
array the ids (like I've done) and so I've to retrieve the data of books in my <Wishlist />
component with filter
and map
, or is it better to save the entire book (or at least, the infos that I need), so that is more easy to have the data?
Which is the best practice, to render the list, in the first case, where I have to retrieve data using filter
and map
?
Here is the attempt:
const Wishlist = () => {
const dispatch = useAppDispatch();
const wishlist = useAppSelector(state => state.user.userInfo.wishlist);
const list = useAppSelector(state => state.catalogue.list);
const renderBooks = () => {
return wishlist.map(wishId => {
list.filter(book => {
if (book.id === wishId) {
return (
<li>{book.title}</li>
)
}
})
})
}
return (
<ul>
{renderBooks()}
</ul>
)
};
export default Wishlist;
Edited - maybe the problem is not the concatenation of methods, but something in the function calling?
Another more little example, with another solution, but same problem
interface fakeObj {
id: string;
name: string;
}
const fakeIds = ['5', '12', '878'] // this is my 'wishlist', containing only ids
const fakeList: fakeObj[] = [ // this is my 'list', containing objects, which were single books
{ id: '334', name: 'Tony' },
{ id: '5', name: 'Milly' },
{ id: '12', name: 'Jack' },
{ id: '7', name: 'Stew' },
{ id: '878', name: 'Mark' },
{ id: '87998', name: 'Greg' }
]
const renderArray = () => {
const arr: fakeObj[] = [];
fakeIds.forEach(id => {
fakeList.filter(obj => {
if (obj.id === id) {
arr.push(obj)
}
})
})
return arr; // the returned array is what I want: an array continaing the objects that i need
}
return (
<ul>
{/* The problem actually is here! */}
{renderArray()}
</ul>
)
You're not using .filter() or .map() correctly, which is what's causing the errors.
The comment above that you rejected was the correct solution to this problem.
The added edited example is even more confusing, because you're no longer trying to return renderable elements, as the return is an array of objects, which can't be rendering in a UL.
So, I'm going back to the initial example.
Problem #1:
You're trying to return a JSX element within the callback passed to .filter(). This is not how .filter() works. You're supposed to pass a callback to .filter() that will return TRUE or FALSE dependent on whether or not you want the element to be filtered in or filtered out. E.g. listOfCounts.filter( count => count > 5);
would return a modified array of all elements in listOfCounts that are greater than 5.
Problem #2:
You're currently calling the .map() first, which is wrong, but then also calling the .filter() WITHIN the .map() instead of chaining the map AFTER the filter. Because right now, your .map() call is not even attempting to return a list of JSX elements representing the book titles you want to render. These calls are meant to be chained opposite of how you have it. An example of how to use .map() that's very simple:
listOfNames.map(name => <div>{name}</div>);
A simple example combining .map() with .filter():
listOfNames.filter(name => name[0] == 'J').map(name => <div>{name}</div>);
which filters out all names that don't start with an uppercase J.
The solution above that Mr. Rafique provided you is correct / very close to correct depending on exact implementation, so I'd suggest you combine this added info with his solution.