React Hook select multiple items with icons - reactjs

I am trying to implement a popover with many items where a user can multi select them. When a user clicks a item, a font-awesome icon is shown to the right.
A user is able to select multiple items and an icon is shown on the right to show it has been checked. This icon toggles when clicking. My problem is my event handler is tied to all the items and whenever I click one, all gets checked.
I am new to hook and react. I am also trying to assign the Id of the selected item in an array. It won't append.
const SettingsComponent = (props) => {
const urlStofTyper = stofTyperUrl;
const stofTyper = [];
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [isItemChecked, setItemChecked] = useState(false);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(null);
const [stoftype, setStoftyper] = useState({ DataList: [] });
const toggle = () => setPopoverOpen(!isPopoverOpen);
const sectionClicked = (e) => {
setItemChecked(!isItemChecked);
let secId = e.target.parentNode.getAttribute("data-section");
if (!isItemChecked) {
stofTyper.push(secId);
} else {
stofTyper.filter((sec) => sec == secId);
}
};
useEffect(() => {
fetchStoftyper({ setError, setLoading, setStoftyper });
}, []);
const fetchStoftyper = async ({ setError, setLoading, setStoftyper }) => {
try {
setLoading(true);
const response = await Axios(urlStofTyper);
const allStofs = response.data;
setLoading(false);
setStoftyper(allStofs);
} catch (error) {
setLoading(false);
setError(error);
}
};
return (
<React.Fragment>
<div className='list-header-icons__fontawesome-icon' id='PopoverClick'>
<FontAwesomeIcon icon={faCog} />
</div>
<Popover
isOpen={isPopoverOpen}
placement='bottom'
toggle={toggle}
target='PopoverClick'>
<PopoverHeader>formatter</PopoverHeader>
<div className='popover-body'>
<ul className='individual-col--my-dropdown-menu-settings'>
{stoftype.DataList.map((item) => (
<li key={item.Id} className='section-items'>
<a
onClick={sectionClicked}
className='dropdown-item'
data-section={item.Sections[0].SectionId}
data-format={
item.Formats.length > 0
? item.Formats[0].FormatId
: ""
}
aria-selected='false'>
<span className='formatter-name'>
{item.Name}
</span>
{isItemChecked && (
<span className='formatter-check-icon'>
<FontAwesomeIcon icon={faCheck} size='lg' />
</span>
)}
</a>
</li>
))}
</ul>
</div>
</Popover>
</React.Fragment>
);

Right now you are using one boolean variable to check if the icon should be displayed, it will not work because every item in your DataList should have it's own individual indicator.
One of the possible solution is to use new Map() for this purposes and store item.id as and index and true/false as a value, so your selected state will be something like this:
Map(3) {1 => true, 2 => true, 3 => false}
After that you can check if you should display your icon as follow:
!!selected.get(item.id)
It will return true if the value in your HashTable is true, and false if it's false or doesn't exist at all. That should be enough to implement the feature you asked for.
For the real example you can check flatlist-selectable section from official Facebook docs They show how to achieve multi-selection using this technique. Hope it helps.

Finally, I have a solution for my own question. Even though, I did not need above solution but I though it would be a good practice to try solve it. Popover is not relevant as that was used only as a wrapper. The below solution can still be placed in a Popover.
CodeSandBox link: Demo here using Class component, I will try to re-write this using hooks as soon as possible.
This solution dependency is Bootstrap 4 and Fontawesome.
import React, { Component } from "react";
import Cars from "./DataSeed";
class DropDownWithSelect extends Component {
constructor(props) {
super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.state = {
isDropDownOpen: false,
idsOfItemClicked: []
};
}
toggleDropdown = evt => {
this.setState({
isDropDownOpen: !this.state.isDropDownOpen
});
};
toggleItemClicked = id => {
this.setState(state => {
const idsOfItemClicked = state.idsOfItemClicked.includes(id)
? state.idsOfItemClicked.filter(x => x !== id)
: [...state.idsOfItemClicked, id];
return {
idsOfItemClicked
};
});
};
render() {
return (
<div className="dropdown">
<button
onClick={this.toggleDropdown}
className="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Dropdown button
</button>
{this.state.isDropDownOpen && (
<div
className="dropdown-menu show"
aria-labelledby="dropdownMenuButton"
>
{Cars.map(x => {
return (
<div
key={x.id}
onClick={() => this.toggleItemClicked(x.id)}
className="dropdown-item d-inline-flex justify-content-between"
>
<span className="d-flex"> {x.name} </span>
<span className="d-flex align-self-center">
{this.state.idsOfItemClicked.includes(x.id) && (
<i className="fa fa-times" aria-hidden="true" />
)}
</span>
</div>
);
})}
</div>
)}
</div>
);
}
}
export default DropDownWithSelect;
Data i.e., ./DataSeed
const cars = [
{
id: 1,
name: "BMW",
cost: 450000
},
{
id: 2,
name: "Audi",
cost: 430000
},
{
id: 3,
name: "Mercedes",
cost: 430000
}
];
export default cars;

Related

Incorrect state when onClick fired

I'm using AlephJS for the first time, and I think I'm running into a closure issue with my code, but for the life of me I can't figure it out. As you can see I have a modal and a page, onClick I'm dispatching a function, but it's using the inital state, instead of the values updated in the modal. I could probably use useRef(), but, I shouldn't have to? Any info or pointers in the right direction would be much appreciated. Code:
import React, {
useEffect,
useState
} from 'react';
import "./main.css";
import { useGames } from "~/lib/gamesContext.tsx";
// import M from "materialize-css";
export default () => {
const { dispatch } = useGames();
const [modalOpen, setModalOpen] = useState(false);
const [newGame, setNewGame] = useState({
players: [],
type: ""
});
const handleContentLoaded = () => {
const selects = document.querySelectorAll('select');
//#ts-ignore
M.FormSelect.init(selects, {});
const chips = document.querySelectorAll('.chips');
//#ts-ignore
M.Chips.init(chips, {
placeholder: 'Enter a Player',
secondaryPlaceholder: '+Player',
onChipAdd: (element: Element, chip: Element) => updatePlayers(element, chip),
onChipDelete: (element: Element, chip: Element) => updatePlayers(element, chip)
});
};
const updatePlayers = (element: Element, chip: Element) => {
//#ts-ignore
const chipData = element[0].M_Chips.chipsData.map(item => item.tag);
setNewGame(newGame => ({
...newGame,
players: chipData
}));
}
const resetState = () => setNewGame({
players: [],
type: ""
});
useEffect(() => {
handleContentLoaded();
}, []);
console.log("New Game", newGame);
return (
<>
<div className={modalOpen ? "custom-modal" : "custom-modal closed"}>
<div className="card modal-start-container">
<div className="card-content">
<span className="card-title">New Game</span>
<div className="input-field col s12">
<select value={newGame.type} onChange={(event) => {
setNewGame(newGame => ({
...newGame,
type: event.target.value
}))
}}>
<option value="" disabled>Choose a game</option>
<option value="rummy">Rummy</option>
<option value="upAndDown">Up and Down</option>
</select>
<label>Game Type</label>
</div>
<div className="chips chips-placeholder"></div>
</div>
<div className="card-action">
<a
onClick={() => {
console.log("on click: ", newGame);
dispatch({
type: "addGame",
payload: {
...newGame,
index: `${newGame.type}${Date.now()}`
}
});
setModalOpen(false);
resetState();
}}
className="waves-effect waves-green btn-flat">Start</a>
</div>
</div>
</div>
<div className={`container ${modalOpen ? "blur" : ""}`}>
<blockquote>
<h4>"Weird how the scorekeeper always wins..."</h4>
<p>- every sore loser everywhere</p>
</blockquote>
<div className="flex-col">
<p className="flow-text">
Today it's easier to find a tablet or phone, than pen and paper. So
we've created an app that will let you keep score of all your
favorite card games, from your favorite digital device.
</p>
<a className="waves-effect waves-light btn" onClick={() => setModalOpen(true)}>
New Game
</a>
</div>
</div>
</>
);
}
Link to code in the TypeScript playground

React : press X button to delete image from screen

I'm a complete beginner in React and I have an app that returns a few pics from an API . I have inserted an 'X' on each pic to delete a pic if user clicks on 'X' . However I have no idea how to use React Hooks to delete a specific image.
My code :
function City(){
// custom func that returns the pics from the api and the loading state of my app
const {loading , pics ,query , setQuery} = useFetch('nature');
//I believe I need something like this but have no idea how to use it (initial state is all
my pics and deleteImage must delete a single pic)
const [images,deleteImage] = useState(pics);
return (<div className="city-info">
{
//if app is loading display loading else return all pics
!loading ?
(pics.length>0 && pics.map((pic) =>{
return <div className="info" key = {pic.id}>
<span className="close">
//I want to use onClick on this button to delete the specific image
<span className="inner-x">
×
</span>
</span>
<img src = {pic.src.original} alt ="img"/>
</div>
})
):<div> Loading </div>
}
</div>);
}
UPDATE : With the code below I remove the images from my list using hooks but they are not deleted from my picture :
function City(){
const {loading , pics ,query , setQuery} = useFetch('athens');
const [images , setImages] = useState([]);
const removeImage = (id) =>{
//images are inserted correctly cannot delete
setImages((oldState)=>oldState.filter((item)=> item.id !== id))
images.map((im)=>console.log(im.id)) //images are deleted from list not from screen
}
useEffect(()=>{
setImages(pics);
} , [pics])
return (<div className="city-info">
{
!loading ?
(pics.length>0 && pics.map((pic) =>{
return <div className="info" key = {pic.id}>
<span className="close" onClick= {()=>removeImage(pic.id)} >
<span
className="inner-x">
×
</span>
</span>
<img src = {pic.src.original} alt ="img"/>
</div>
})
):<div> Loading ... </div>
}
</div>);
}
export default City;
So this is a simple local state example on how to remove items inside of an array
import React, { useState, useEffect } from "react";
import "./styles.css";
const allImages = [
{
id: 1,
imgUrl: "https://via.placeholder.com/100"
},
{
id: 2,
imgUrl: "https://via.placeholder.com/100"
},
{
id: 3,
imgUrl: "https://via.placeholder.com/100"
}
];
const App = () => {
const [pics, setPics] = useState([]);
const removeImage = (id) => {
// this is the line that you are looking for
setPics((oldState) => oldState.filter((item) => item.id !== id));
};
useEffect(() => {
//fake fetch data
setPics(allImages);
}, []);
return (
<div className="App">
{pics.map((pic) => {
return (
<div style={{ marginBottom: "100px" }}>
{pic.id}
<img
src={pic.imgUrl}
width="100px"
height="100px"
alt="placeholder grey 100px"
/>
<button onClick={() => removeImage(pic.id)}>X</button>
</div>
);
})}
</div>
);
};
export default App;
sandbox link

React array mapping, toggles all drop-downs on click, I want to open the dropdown for the clicked card only

TextQuoteCard
import React, {useRef, useState} from 'react'
import {Link} from "react-router-dom";
import {QuoteCardDropdown} from "../../utils/dropdowns";
export const TextQuoteCard = () => {
const [open, setOpen] = useState(false)
const toggle = () => setOpen(!open)
const [textQuote, setTextQuote] = useState([
{
userId: '123',
userName: 'Tr',
userImageUrl: 'https://qph.fs.quoracdn.net/main-thumb-892821828-200-lrcgeycqieflgsovvoxglqawinbcjhtv.jpeg',
quoteId: 'TQ122',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Jhon Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'If there’s no market, about finding market opportunities, or creating opportunities. If there’s no market, then you need to grow one',
quoteImageUrl: 'https://qph.',
bookmarkStatus: 2,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
},
{
userId: '124',
userName: 'nr',
userImageUrl: 'https://qph.fi.jpeg',
quoteId: 'TQ123',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Wall Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'Best thing to do. ',
quoteImageUrl: '',
bookmarkStatus: 1,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
}
])
const handleBookmark = (event) => {
console.log(event)
}
const idGetter = (id) =>{
console.log(id)
}
const test = Object.keys(textQuote).map(item => item)
console.log(test)
return(
<div>
{
textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(!open)}
>
options
</span>
</span>
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))
}
</div>
)
}
**
QuoteCardDropdown.js
import React, {useState} from 'react'
import {Link} from "react-router-dom";
import '../../global/assets/css/dropdowns.css'
export const QuoteCardDropdown = (props) => {
const [ddItems, SetDdItems] = useState([
{
ddOptionIcon: 'icon',
ddOptionText: 'Share',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
},
{
ddOptionIcon: 'icon',
ddOptionText: 'Bookmark',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
}
])
return (
<div>
<div className="quoteCardDropdownPrimaryContainer">
<div className="quoteCardDropdownPrimaryBody">
<div className="quoteCardDropdownPrimaryBodyInner">
{
ddItems.map(item => (
<Link to=
{
item.ddOptionText === 'Edit this Quote' ?
`${'edit/' + props.targetLink}` :
item.ddOptionText === 'Share' ?
`${'share/' + props.targetLink}` : ''
}
>
<div className="quoteCardDropdownContentWrapper">
<div className="quoteCardDropdownContentItem">
<div className="quoteCardDropdownItem" key={item.ddOptionText}>
{item.ddOptionText}
</div>
</div>
</div>
</Link>
))
}
</div>
</div>
</div>
<div className="quoteCardPointer" data-placement='top'> </div>
</div>
)
}
I have array of objects mapping to which showed multiple card on-page/feed. each card has a dropdown that the user can perform several actions for the clicked card. think of FB feed or any other social media feed card that the user can click to open a dropdown and pick option for the card. I am trying to achieve something similar but the problem is when I click on the button to open the dropdown it toggles all the dropdowns for all the cards instead of opening the dropdown for the clicked card.
Expected Behavior: open the dropdown for the clicked card only.
Change the open to take the id:
const [open, setOpen] = useState() // undefined is nothing open
const toggle = id => setOpen(open === id ? undefined : id) // close if currently open
// the JSX
return(
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}>
options
</span>
</span>
{open === quote.quoteId && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))}
</div>
)
Yes, you are trying to control all dropdowns using a single state variable open.
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
When you click on any dropdown it will toggles open and then all dropdowns will open because that single variable controls all of them.
Instead, what you can do is maintain a separate state variable for each dropdown.
I have an example to maintain separate state variable for dropdown-
toggle = (index) => {
this.setState(prevState => {
[`open+${index}`]: !prevState[`open${index}`]
}
}
This way you can keep track of/or toggles open for particular dropdown you just need to change below code -
{
textQuote.map((quote, index) => ( //add 2nd parameter as index
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => this.toggle(index)}
>
options
</span>
</span>
{ this.state[`open${index}`] && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
</div>
))
}
Note - Unfortunately I am not aware of handling state as dynamically inside the function component, but I have given you the exact same use case using class component.
Use an object indexed by quote ID to track a card's open status by the quote ID. I copied only the relevant code, make sure this has all of the other code you need:
export const TextQuoteCard = () => {
const [openById, setOpenById] = useState({});
const toggle = (quoteId) => {
setOpenById((currOpenById) => {
const nextOpenById = { ...currOpenById };
if (currOpenById[quoteId]) {
delete nextOpenById;
} else {
nextOpenById[quoteId] = true;
}
return nextOpenById
})
}
// ...removed code not relevant to example
return (
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span
className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}
>
options
</span>
</span>
{openById[quote.quoteId] && <QuoteCardDropdown targetLink={quote.quoteId} />}
</div>
</div>
))}
</div>
);
};

Reset pagination to the first page by clicking a button outside the component

I'm using material UI usePagination hook to create a custom pagination component, so far so good, the functionality works as expected but I was wondering how I can be able to reset the pagination to the first page by triggering a button that is not part of the pagination component.
Does anyone has an idea on how to trigger that?
This is my component.
import React from "react";
import PropTypes from "prop-types";
import { usePagination } from "hooks";
function arrow(type) {
return (
<i
className={`fa fa-chevron-${
type === "next" ? "right" : "left"
} page-icon`}
/>
);
}
function Pagination({ data, itemCount, onChange }) {
const { items } = usePagination({
count: Math.ceil(data.length / itemCount, 10),
onChange
});
return (
<nav aria-label="Paginator">
<ul className="pagination-component">
{items.map(({ page, type, selected, ...item }, index) => {
let children;
if (type === "start-ellipsis" || type === "end-ellipsis") {
children = "…";
} else if (type === "page") {
children = (
<button
type="button"
automation-tag={`page-${page}`}
className={`page-button ${selected ? "selected" : ""}`}
{...item}
>
{page}
</button>
);
} else {
children = (
<button
automation-tag={type}
className="page-button"
type="button"
{...item}
>
<span className="d-none">{type}</span>
{arrow(type)}
</button>
);
}
return (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className="page-item">
{children}
</li>
);
})}
</ul>
</nav>
);
}
What I'm trying is to create a select component that the onChange function will sort the data, depending on the selection, but when the data is sorted I want to return the pagination component to the first page
const TableVizContainer = props => {
const [currentPage, setCurrentPage] = useState(1);
const [sortColumn, setSortColumn] = useState(1);
const [range, setRange] = useState({
start: 0,
end: 25
});
const onChangePage = (_event, page) => {
setCurrentPage(page);
setRange({
start: 25 * (page - 1),
end: 25 * page
});
};
const onSelectChange = event => {
const { value } = event.target;
setCurrentPage(1);
setSortColumn(parseInt(value, 10));
};
return (
<div
className="table-viz-container container-fluid my-4 float-left"
automation-tag={`table-viz-${automationId}`}
>
<div className="d-flex justify-content-between mb-3 leaderboard-meta">
<span className="leaderboard-title">{visualization.title}</span>
<div className="mr-5">
<label htmlFor="sort-table-select">
Sort By:
<select
id="sort-table-select"
onChange={onSelectChange}
value={sortColumn}
>
{visualization.columns.map((column, index) => {
const uniqueId = uuidv1();
return (
<option key={uniqueId} value={index}>
{setSelectValue(column, visualization.metrics)}
</option>
);
})}
</select>
</label>
</div>
</div>
<div className="d-block d-sm-flex justify-content-between align-items-center my-2 px-2">
<span className="page-items-count" automation-tag="pagination-count">
{`Showing ${range.start === 0 ? 1 : range.start + 1} - ${
range.end <= visualization.rows.length
? range.end
: visualization.rows.length
} of ${visualization.rows.length}.`}
</span>
<Pagination
currentPage={currentPage}
data={visualization.rows}
itemCount={25}
onChange={onChangePage}
/>
</div>
</div>
);
};
Does anyone has an idea on how to reset and move the pagination page to the first one without clicking the component?
There are two ways.
1. Passing Props
Let's just say you have a function called jump() and passing 1 as an argument will reset the pagination. So, you can pass the jump function as a property and reuse that on other components.
function jump(){
setCurrentPage(1)
}
<MyCompnent resetPage={jump} />
// MyComponent
function MyComponent({resetPage}){
return (
<button onClick={resetPage(1)}></button>
)
}
2. On Changing Route
You can reset your pagination when your route will change. For example, you are using a router npm package and that package has a method called onChange or routeChangeStart. With those methods or after creating that method you can implement a function like below.
Router.events.on("routeChangeStart", () => {
jump(1);
});

Update list of displayed components on deletion in React

in the beginning on my path with React I'm creating simple to-do app where user can add/remove task which are basically separate components.
I create tasks using:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
I render list of components (tasks) using following method:
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask = {(id) => this.removeTask(id)}
key = {index}/>;
})
);
}
method to remove specific task takes unique ID of task as an argument and based on this ID I remove it from the tasks list:
removeTask(uID){
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != uID )
}));
}
But the problem is, when I delete any item but the last one, it seems like the actual list of components is the same only different objects are passed to those components.
For example:
Lets imagine I have 2 created componentes, if I set state.Name = 'Foo' on the first one, and state.Name='Bar' on the second one. If I click on remove button on the first one, the object associated to this component is removed, the second one becomes first but it's state.Name is now 'Foo' instead of 'Bar'.
I think I'm missing something there with correct creation/removing/displaying components in react.
Edit:
Method used to remove clicked component:
removeCurrentTask(){
this.props.removeTask(this.props.taskObj.id);
}
SingleTask component:
class SingleTask extends Component{
constructor(props) {
super(props);
this.state={
showMenu : false,
afterInit : false,
id: Math.random()*100
}
this.toggleMenu = this.toggleMenu.bind(this);
}
toggleMenu(){
this.setState({showMenu : !this.state.showMenu, afterInit : true});
}
render(){
return(
<MDBRow>
<MDBCard className="singleTaskContainer">
<MDBCardTitle>
<div class="priorityBadge">
</div>
</MDBCardTitle>
<MDBCardBody className="singleTaskBody">
<div className="singleTaskMenuContainer">
<a href="#" onClick={this.toggleMenu}>
<i className="align-middle material-icons">menu</i>
</a>
<div className={classNames('singleTaskMenuButtonsContainer animated',
{'show fadeInRight' : this.state.showMenu},
{'hideElement' : !this.state.showMenu},
{'fadeOutLeft' : !this.state.showMenu && this.state.afterInit})}>
<a
title="Remove task"
onClick={this.props.removeTask.bind(null, this.props.taskObj.id)}
className={
classNames(
'float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightRed'
)
}
>
<i className="align-middle material-icons">remove</i>
</a>
<a title="Edit title"
className={classNames('show float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightBlue')}
>
<i className="align-middle material-icons">edit</i>
</a>
</div>
</div>
{this.props.taskObj.description}
<br/>
{this.state.id}
</MDBCardBody>
</MDBCard>
</MDBRow>
);
}
}
Below visual representation of error, image on the left is pre-deletion and on the right is post-deletion. While card with "22" was deleted the component itself wasn't deleted, only another object was passed to it.
Just to clarify, the solution was simpler than expected.
In
const showTasks = () => taskList.map((item, index) => (
<SingleTask
taskObj={item}
removeTask ={removeTask}
key = {item.id}
/>
)
)
I was passing map index as a key, when I changed it to {item.id} everything works as expected.
In short, in the statement tasksList.push(<SingleTask taskObj={taskObj} removeTask ={this.removeTask}/>);, removeTask = {this.removeTask} should become removeTask = {() => this.removeTask(taskObj.id)}.
However, I would reconsider the way the methods addTask and showTasks are written. While the way you have written isn't wrong, it is semantically unsound. Here's what I would do:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
const SingleTask = (task) => {
const { taskObj } = task;
return <div onClick={task.removeTask}>
{ taskObj.title }
</div>
}
// Example class component
class App extends React.Component {
state = {
tasksList: [
{ id: 1, title: "One" },
{ id: 2, title: "Two" },
{ id: 3, title: "Three" },
{ id: 4, title: "Four" }
]
}
addTask = (taskObj) => {
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks = () => {
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
key={index}
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
removeTask(id) {
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != id )
}));
}
render() {
return (
<div className="App">
<div> {this.showTasks()} </div>
</div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.body
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Resources