Removing a specific element in an array with React - reactjs

I have this code
I have some sections and inside it I have an item, number and button. How can I remove some specific item?
I'm rendering the sections as:
{sections.map((section, index) => (
<Section
section={section}
key={section.id}
addItem={(item) => addItem(index, item)}
removeItem={(i) => removeItem(index, i)}
/>
))}
And in my Section component, I'm rendering it as:
{section.items.map((item, i) => (
<>
<h2>{item}</h2>
<h3>{section.number[i]}</h3>
<button onClick={() => removeItem(i)}>Remove</button>
</>
))}
The remove function is in the Section's parent component and is:
const removeItem = (index, i) => {
let filteredItem = sections[index].items.splice(i);
let filteredNumber = sections[index].number.splice(i);
setSections((prev) => ({ ...prev, filteredItem, filteredNumber }));
};
When I click the remove button it says that sections.map is not a function. What am I doing wrong?

At glance looks like you have an error in your Section component. The code:
{section.items.map((item, i) => (
<>
<h2>{item}</h2>
<h3>{section.number[i]}</h3>
<button onClick={() => removeItem(i)}>Remove</button>
</>
))}
should be:
{section.items.map((item, i) => (
<>
<h2>{item.title}</h2>
<h3>{item.number[i]}</h3>
<button onClick={() => removeItem(i)}>Remove</button>
</>
))}
Because you're passing down item and you're trying to render everything in the item object and section.number should be item.number.

I see your problem.
In the code below when removing an element you are setting the sections to a single object not an array anymore. so map() doesn't exist on an Object. You have to convert it back into an Array.
const removeItem = (index, i) => {
let filteredItem = sections[index].items.splice(i);
let filteredNumber = sections[index].number.splice(i);
setSections((prev) => ({ ...prev, filteredItem, filteredNumber }));
};
Edit:
Upon further inspection of your code I see more errors.
In the remove item section in Section.js i see your trying to
const itemTarget = section.items[i];
That seems to be cause you are acting as is section is one object. but its an array that already has one section so you have to call it as follows for it to grab the items from the first (default) section.
const itemTarget = section[0].items[i];
This is the same with the filtered variable you will have to make sure when removing the item you are removing it from the correct section aswell.

Related

scroll to div using useRef in map method

i am using useRef to scroll to specific div but it is not working in map method's case (probably because of id) , so can anyone tell me how to provide id. it is taking last element in map method right now.
this is element to which i want to scroll to.
{allMessages?.map((message) => (
<div
key={message.data.id}
ref={filterRef}>
<div>
<p>{message.data.text}</p>
</div>
</div>
))}
this is filtered data in which i am getting filtered messages and clicks on specific div.
{filteredMsg?.map((item) => (
<li
onClick={() => goToFilterData(item.data.id)}
key={item.data.id}
>
{item.data.text}
</li>
))}
this is what i have done with useRef yet -
const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);
const goToFilterData = (id) => {
scrollToRef(filterRef);
};
Just pass an id to every element that maybe should scroll into the view.
{allMessages?.map((message) => (
// the element you want to scroll to
<div id={`message_${message.someUniqueIdentifier}`} />
))}
Pass the identifier to the scroll function.
{filteredMsg?.map((item) => (
<li onClick={() => goToFilterData(item.data.someUniqueIdentifier)}>
{item.data.text}
</li>
))}
Query the element and make it scroll into the view.
const goToFilterData = (uniqueIdentifier) => {
const element = document.getElementById('message_' + uniqueIdentifier);
element.scrollIntoView()
};
Note: ofc you could handle this with a lot of single refs and pass them, but this should just work fine.

Possible to render components in reverse manner in JSX?

In my Nextjs/React.js component I am rendering list of cards like this :
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
<div className="lg:col-span-8 col-span-1">
{posts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}
</div>
I was wondering if it was possible to render these PostCards in a reverse manner; starting from the last index rather than the initial index? This is for my blog application and whenever I post a new PostCard I want the latest Post to be rendered on Top of the list instead of the bottom of the list.
Just reverse the array first:
{posts.slice(0).reverse().map((post, index) => (
<PostCard
post={ post.node }
key={ post.title }
/>
))}
whenever I post a new PostCard I want the latest Post to be rendered on Top of the list instead of the bottom of the list.
If you're currently doing this by adding to the end of an array, you can add to the start of it instead. For example, if you're doing this:
setPosts(prev => [...prev, { title: 'My New Post' }]);
Do this instead:
setPosts(prev => [{ title : 'My New Post' }, ...prev]);
If you can't change the way the array gets created (say, because some components want it in one order, and some in another), then you can create a new array in the right order, and then map over that. You may want to memoize this if the array isn't going to change often:
const reorderedPosts = useMemo(() => {
return [...posts].reverse();
}, [posts]);
// ...
{reorderedPosts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}
This can also easily be enhanced to let you change the order via a state, if you need to:
const [shouldReverse, setShouldReverse] = useState(false);
const reorderedPosts = useMemo(() => {
if (shouldReverse) {
return [...posts].reverse();
} else {
return posts;
}
}, [posts, shouldReverse])
// ...
{reorderedPosts.map((post, index) => (
<PostCard post={post.node} key={post.title} />
))}

React onClick() doesn't trigger when inside map function

I'm building my first react application and I've got an issue.
I want to create a simple app where I can click on a book title in a sidebar, and see a summary of the book. I'm getting the book titles and summaries through an API I've defined locally.
Right now, clicking on a book title doesn't do anything. I get an error in red in the console:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
I'm calling my API like so:
const [viewingId, setViewingId] = useState()
const [bookList, setBookList] = useState([])
const [contents, setContents] = useState({})
useEffect(() => {
fetch("http://localhost:5000/books")
.then(res => res.json())
.then(res => {setBookList(res)})
}, [])
const fetchBookSummary = (id) => {
fetch(`http://localhost:5000/book/${id}`)
.then(res => res.json())
.then(res => {
setContents({...contents, [id]: res[0]})
})
}
This is how I'm defining my 'onClick' function:
const seeSummary = (id) => {
fetchBookSummary(id)
setViewingId(id)
}
And finally, my App renders like this:
return (
<div className="App">
<div className="App-Container">
<div className="BookList">
{bookList.map(book => (
<Node book={book} onClick={() => seeSummary(book.id)} />
))}
</div>
<div className="SummaryView">
{contents[viewingId]?.content?.map((summaryData => (
<Summary summaryData={summaryData}/>
)))}
</div>
</div>
</div>
);
}
I used to have just onClick={seeSummary(book.id)}, but I got a "too many re-renders" error...I'm not sure I understand what adding () => did.
I'd like to be able to see the Summary when I click on a book. Any help is appreciated!
To resolve the React key warning you just need to provide a valid React key to the elements being mapped.
{bookList.map(book => (
<Node
key={book.id} // <-- Add a unique key, the book id is good
book={book}
onClick={() => seeSummary(book.id)}
/>
))}
When you were just onClick={seeSummary(book.id)} this was invoking the callback immediately, thus causing the render looping. By adding the "() =>" you are declaring an anonymous function to invoke your seeSummary function and pass the specific book id at-the-time the node is clicked.
I don't see any overt issues with the way you've defined your onClick handler other than the fetching book details will take longer than it will take to update the summary id.
const seeSummary = (id) => {
fetchBookSummary(id); // fetching data
setViewingId(id); // will update first.
}
I suspect you haven't connected/attached the onClick prop of the Node component to any DOM element, like a button or div, etc... You need to ensure that the onClick prop is actually attached to something a user can interact with.
Example:
const Node = ({ book, onClick }) => (
<div onClick={onClick}>
{book.title} - Click me to see summary!
</div>
);

Is there any pitfall of using ref as conditional statement?

Context: I am trying to scroll view to props.toBeExpandItem item which keeps changing depending on click event in parent component. Every time a user clicks on some button in parent component I want to show them this list and scroll in the clicked item to view port. Also I am trying to avoid adding ref to all the list items.
I am using react ref and want to add it conditionally only once in my component. My code goes as below. In all cases the option.id === props.toBeExpandItem would be truthy only once in loop at any given point of time. I want to understand will it add any overhead if I am adding ref=null for rest of the loop elements?
export const MyComponent = (
props,
) => {
const rootRef = useRef(null);
useEffect(() => {
if (props.toBeExpandItem && rootRef.current) {
setTimeout(() => {
rootRef.current?.scrollIntoView({ behavior: 'smooth' });
});
}
}, [props.toBeExpandItem]);
return (
<>
{props.values.map((option) => (
<div
key={option.id}
ref={option.id === props.toBeExpandItem ? rootRef : null}
>
{option.id}
</div>
))}
</>
);
};
Depending upon your recent comment, you can get the target from your click handler event. Will this work according to your ui?
const handleClick = (e) => {
e.target.scrollIntoView()
}
return (
<ul>
<li onClick={handleClick}>Milk</li>
<li onclick={handleClick}>Cheese </li>
</ul>
)

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