Deleting Elements in array by index using Immer in React - javascript

I'm building this component using React where I can Add Delete and edit lessons and sections using immer library. However, when I add a new section I cant seem to delete a specific Lesson in the section, it deletes the last lesson created.
And Deleting a specific section is not working as well. Can anyone give me a hint to this problem?
These are the two deletion function that are giving me a hard time:
remove = (sectionIndex, lessonIndex) => {
const nextState = produce(this.state, (draftState) => {
draftState.list[sectionIndex].lessons.splice(lessonIndex, 1);
});
this.setState(nextState);
this.id++;
};
deletesection(sectionIndex, i) {
const nextState = produce(this.state, (draftState) => {
draftState.list[sectionIndex].section.splice(i, 1);
});
this.setState(nextState);
this.id++;
}
Here is the a link to the sandbox reproduction code: https://codesandbox.io/s/serene-forest-hpv7r?file=/src/TestClonereact.jsx

remove actually seemed to be working for me, but I spotted some errors with deletesection:
The function takes two arguments (both of which seem to be the section index), but you only call it with one.
It's not an arrow function, so it will have its own this and won't be able to access this.state.
You are accessing a property .section which does not seem to exist.
Instead of splice you would want to remove the whole section object from the draftState.list array.
deletesection = (sectionIndex) => {
const nextState = produce(this.state, (draftState) => {
delete draftState.list[sectionIndex];
});
this.setState(nextState);
}
My personal preference would be use curried functions rather than passing the sectionIndex all the way down to the Lesson component. Also you can use produce inside a setState callback rather than accessing this.state directly. But those are just suggestions. Here's my tweaked version:
import React from "react";
import "./styles.css";
import EdiText from "react-editext";
import produce from "immer";
import { v4 as uuid } from "uuid";
const Lesson = ({ lesson, onSave, remove }) => {
const { id } = lesson;
return (
<div key={id} id={`sectionlesson-${id}`}>
<div className="section-titles">
<i className="material-icons" id="iconsectionlist" type="button">
list
</i>
<EdiText
type="text"
value="Lesson Title"
onSave={onSave}
key={id}
id={`lesson-${id}`}
/>
<i className="material-icons" id="iconsectiondel" type="button">
text_fields
</i>
<i className="material-icons" id="iconsectiondel" type="button">
smart_display
</i>
<i
className="material-icons"
id="iconsectiondel"
onClick={remove}
type="button"
>
delete
</i>
</div>
<div className="testh"></div>
</div>
);
};
const Section = ({ section, onSave, remove, addlesson, deletesection }) => {
const { id } = section;
return (
<div key={id} id={`sds-${id}`}>
<div className="course-structure-form" key={id} id={`csf1-${id}`}>
<div className="section-heading">
<i className="material-icons" id="iconsection">
api
</i>
<EdiText type="text" value="Section Title" onSave={onSave} />
</div>
{section.lessons.map((lesson, lessonIndex) => (
<Lesson
key={lesson.id}
lesson={lesson}
remove={remove(lessonIndex)}
onSave={onSave}
/>
))}
<div className="addnewlesson" onClick={addlesson}>
<i
className="material-icons"
id="iconsectionde"
role="button"
type="button"
>
add_circle
</i>
<span>Add New Lesson</span>
</div>
<button onClick={deletesection}>Delete Section</button>
</div>
</div>
);
};
class TestClonereact extends React.Component {
constructor(props) {
super(props);
this.state = {
list: []
};
}
onSave = (val) => {
console.log("Edited Value -> ", val);
};
lesson({ id }) {}
addsection = () => {
this.setState(
produce((draftState) => {
draftState.list.push({ id: uuid(), lessons: [] });
})
);
};
addlesson = (sectionIndex) => () => {
this.setState(
produce((draftState) => {
// needs to have a unique id
draftState.list[sectionIndex].lessons.push({ id: uuid() });
})
);
};
remove = (sectionIndex) => (lessonIndex) => () => {
this.setState(
produce((draftState) => {
draftState.list[sectionIndex].lessons.splice(lessonIndex, 1);
})
);
};
deletesection = (sectionIndex) => () => {
this.setState(
produce((draftState) => {
delete draftState.list[sectionIndex];
})
);
};
render() {
return (
<div>
{this.state.list.map((section, i) => (
<Section
key={section.id}
section={section}
remove={this.remove(i)}
addlesson={this.addlesson(i)}
onSave={this.onSave}
deletesection={this.deletesection(i)}
/>
))}
<div className="add-section-button-structure">
<button className="tablink" onClick={this.addsection}>
Add New Section
</button>
<button className="tablink">Clear</button>
<button className="tablink">Preview</button>
<button className="tablink">Submit</button>
</div>
</div>
);
}
}
export default TestClonereact;
Code Sandbox Link

Related

How do you remove a CSS class from a certain element of a list with React

I'm trying to remove a CSS class from a specific item when clicking on that item's button. Removing the CSS class will make a menu appear. How would I go about doing this with React? Here's the code.
import "./Homepage.css"
import React, { useState, useEffect, useRef } from "react"
// import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
// import { faArrowDown } from "#fortawesome/free-solid-svg-icons"
import { Link } from "react-router-dom"
import useFetch from "./useFetch"
import Axios from "axios"
export default function Homepage() {
const [body, setBody] = useState("")
const [sortedData, setSortedData] = useState("")
const [data, setData] = useState("")
const [css, setCss] = useState("")
const [flash, setFlash] = useState(null)
const posts = useFetch("http://localhost:5000/api/data")
const firstRender = useRef(true)
useEffect(() => {
let test = JSON.parse(window.localStorage.getItem("user"))
console.log(test)
setData(posts)
}, [posts])
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
return
}
data.sort(function (a, b) {
return new Date(b.date) - new Date(a.date)
})
setSortedData(data)
}, [data])
const handleSubmit = (e) => {
e.preventDefault()
Axios.post("http://localhost:5000/api/react-create-post", { text: body }, { withCredentials: true })
.then((res) => {
setSortedData((prevArray) => [res.data.post, ...prevArray])
setFlash("Successfully created post.")
setCss("success-msg")
setBody("")
})
.catch((err) => {
setCss("error-msg")
setFlash("Field cannot be left blank.")
})
}
const handleClick = (e) => {
e.preventDefault()
e.target.parentElement.children[1]
}
return (
<div>
<center>
<div className="create-container">
<div className="posts-title">Create Post</div>
<form id="theForm" onSubmit={(e) => handleSubmit(e)}>
<textarea onChange={(e) => setBody(e.target.value)} value={`${body}`} id="theInput" className="post-input" name="text" type="text"></textarea>
<button className="submit-btn">POST</button>
</form>
</div>
<div id="postsContainer" className="posts-container">
<div className="posts-title">Latest Posts</div>
{flash ? <div className={css}>{flash}</div> : console.log()}
<div id="postInput">
{sortedData &&
sortedData.map((item) => {
return (
<div className="post-container" key={item._id}>
<Link className="a" to={`/user/${item.author}`}>
<h3 className="author">{item.author}</h3>
</Link>
<div className="date">{item.date.toLocaleString()}</div>
<div className="options-cont">
<button onClick={(e) => handleClick(e)} id="optionsBtn" className="options-btn">
<i className="fas fa-ellipsis-v"></i>
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} data-text={`${item.body}`} id="editBtn" className="edit inside-btn invisible">
Edit
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} id="deleteBtn" className="delete inside-btn invisible">
Delete
</button>
<br></br>
</div>
<p className="body-text">{item.body}</p>
</div>
)
})}
</div>
</div>
</center>
</div>
)
}
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
As pilchard said, you probably want to make each of those its own component with its own "showing" state, or at least "showing" prop.
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
That would be true if you used a single flag in state. But instead, use a set of flags, one flag for each menu, perhaps keyed by item._id.
Assuming you don't do the refactoring pilchard (and I) suggest:
You haven't shown us enough code for me to know whether you're using class components or function components, so I'm going to guess function components with hooks. If so, the initial state would be:
const [showing, setShowing] = useState(new Set());
Then when rendering, you'd assign the class:
<theElement className={showing.has(item._id) ? "class-if-any-to-show-it" : "class-if-any-to-not-show-it" ...
To toggle, in the button pass the ID:
<button onClick={(e) => handleClick(e, item._id)}
and then update state as appropriate:
const handleClick = (e, id) => {
e.preventDefault()
setShowing(showing => {
showing = new Set(showing);
if (showing.has(id)) {
showing.delete(id);
} else {
showing.add(id);
}
return showing;
});
};

Using React Hooks How Can I Have two search inputs that work together to filter results

I apologize if this is unclear. Let me know if any of this need clarification.
I have two search input. One that sorts by name and another that sorts by tags. Filtering the names was easy enough since I was grabbing that data from an API. However, filtering the data by tags is proving difficult. What would be the best way to set this up?
I have three main components: Search.js Profile.js and Tags.js. Search just passes the user input down. Profile loops over the APi data and filters by name. The Tags component allows the user to add and remove tags. This is placed inside the .map in profile. Since the tags component is inserted into the profile it creates the desired effect of allowing each one to have it's own set of tags, but I can't figure out to transfer the tags info in to state and then filter the profiles that have the searched tag.
Profile Component
const createProfile = (profile) => {
const gradesToNum = profile.grades.map((num) => parseInt(num, 10));
const getAverage = gradesToNum.reduce((a, b) => a + b) / gradesToNum.length;
const getAllGrades = profile.grades.map(renderGrades);
return (
<div key={profile.id} className="profileWrapper">
<div className="profileCard">
<div className="studentImg">
<img src={profile.pic} alt={profile.firstName} />
</div>
<div className="studentBio">
<h3>
{profile.firstName} {profile.lastName}
</h3>
<ul className="studentInfo">
<li>Email: {profile.email}</li>
<li>Company: {profile.company}</li>
<li>Skill: {profile.skill}</li>
<li>Average: {getAverage}%</li>
</ul>
<div className={shownGrades[profile.id] ? 'show' : 'hide'}>
<ul>{getAllGrades}</ul>
<Tags />
</div>
</div>
</div>
<button className="expand-btn" onClick={() => toggleGrade(profile.id)}>
{shownGrades[profile.id] ? (
<i className="fas fa-minus"></i>
) : (
<i className="fas fa-plus"></i>
)}
</button>
</div>
);
};
const getProfile = () =>
props.students.filter(props.filterByName).map(createProfile);
return <section className="wrapper">{getProfile()}</section>;
**Tags Component**
```const Tags = (props) => {
const [tags, setTags] = useState([]);
const addTag = (e) => {
if (e.key === 'Enter' && e.target.value.length > 0) {
setTags([...tags, e.target.value]);
e.target.value = '';
}
};
const removeTags = (indexToRemove) => {
setTags(
tags.filter((x, index) => {
return index !== indexToRemove;
})
);
};
return (
<div className="tags-input">
<ul>
{tags.map((tag, index) => {
return (
<li key={index}>
<span>{tag}</span>
<i className="fas fa-times" onClick={() => removeTags(index)}></i>
</li>
);
})}
</ul>
<input
type="text"
placeholder="press enter to add tag"
onKeyUp={addTag}
id="tag-input"
/>
</div>
);
};```
If you have more complex state handling it's a good practice to use React's useReducer instead of useState for your TagsComponent. https://reactjs.org/docs/hooks-reference.html#usereducer
You can simplify your TagsComponent like:
const initialState = [];
function reducer(state, action) {
switch (action.type) {
case 'addTag':
return [...state, action.payload];
case 'removeTag':
return state.filter(tag => tag !== action.payload)
default:
throw new Error();
}
}
const Tags = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleAddTag = event => {
if (e.key === 'Enter' && e.target.value.length > 0) {
dispatch({type: 'addTag', payload: e.target.value});
e.target.value = '';
}
}
};
return (
<div className="tags-input">
<ul>
{tags.map((tag, index) => {
return (
<li key={index}>
<span>{tag}</span>
<i className="fas fa-times" onClick={() => dispatch({type: 'removeTag', payload: index})}></i>
</li>
);
})}
</ul>
<input
type="text"
placeholder="press enter to add tag"
onKeyUp={handleAddTag}
id="tag-input"
/>
</div>
);
};

React js conditionally rendering a class to a specific mapped item

I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};

Delete button - onDelete function

My delete button worked before but now stopped working when I added a like/dislike satisfaction section for every challenge. Can someone spot the bug for me? This is message I get in the terminal when pushing the button: DELETE /allchallenges/[object%20Object] 200 1.623 ms - 214-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
import React from 'react'
import DefaultLayout from "../layout/Default"
import Challengebox from '../components/Challengebox'
import axios from "axios";
import "./Allchallenges.css"
import { faThumbsUp } from "#fortawesome/free-solid-svg-icons";
import { faThumbsDown } from "#fortawesome/free-solid-svg-icons";
import { faBalanceScale } from "#fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
class Allchallenges extends React.Component {
constructor() {
super()
this.state = {
challenges: []
}
this.onDelete=this.onDelete.bind(this)
this.sortByTitle=this.sortByTitle.bind(this)
this.sortByDescription=this.sortByDescription.bind(this)
this.searchChallenges=this.searchChallenges.bind(this)
this.challengestotal=this.challengestotal.bind(this)
}
componentDidMount(){
axios({
method: "GET",
url: `${process.env.REACT_APP_API_BASE}/allchallenges`,
withCredentials: true
})
.then(response => {
console.log(response)
let challengeslist = response.data;
this.setState({challenges: challengeslist})
})
.catch(error => {
console.log("You've made an error charles: ",error)
})
}
onDelete(challengeId){
axios
.delete(`${process.env.REACT_APP_API_BASE}/allchallenges/${challengeId}`)
.then(response => {
const challenges = this.state.challenges.filter(challenge => challenge._id !== challengeId)
this.setState({challenges})
})
.catch(err => console.log(err))
}
sortByTitle() {
let challengesSortTitle = this.state.challenges.sort((a,b) => {
return a.title > b.title ? 1 : -1
})
this.setState({
challenges:challengesSortTitle
})
}
sortByDescription() {
let challengesSortDescription = this.state.challenges.sort((a,b) => {
return a.description > b.description ? 1 : -1
})
this.setState({
challenges:challengesSortDescription
})
}
searchChallenges(e){ // eslint-disable-next-line
let challengesSearch = this.state.challenges.filter(challenge => {
if(challenge.title){
if(challenge.title.toLowerCase().includes(e.target.value.toLowerCase())){
return true
}
}
})
this.setState({
challenges:challengesSearch
})
}
challengestotal(){
return `${this.state.challenges.length}`
}
// handleLikeDislike(e){
// e.preventDefault()
// }
render(){
return (
<DefaultLayout>
<div className="challengeoverviewlist">
<h1>All challenges</h1>
<div className="headers">
<button onClick={this.sortByTitle} className="sorttitle">
Sort based on TITLE
</button>
<button onClick={this.sortByDescription} className="sortdescription">
Sort based on DESCRIPTION
</button>
<button onClick={this.sortByDescription} className="sortdescription">
Sort based on DAREDEVILS
</button>
<input className="searchbox" type="text" placeholder="Search for a challenge title here..." onChange={this.searchChallenges} />
<p className="challengescounterbox">{this.challengestotal()} challenges</p>
</div>
<div className="challengeboxes">
{
this.state.challenges.map(challenge =>
(
<div className="totalbox" key={challenge._id}>
<div className="likedislikesbox">
<div className="likecontainer">
<div className="leftalignment"><FontAwesomeIcon icon={faThumbsUp} /></div>
{/* onClick={(e)=> this.handleLikeDislike(e)} */}
<p className="likestat">Likes{challenge.likes}</p>
</div>
<div className="dislikecontainer">
<div className="leftalignment"><FontAwesomeIcon icon={faThumbsDown}/></div>
<p className="dislikestat">Dislike</p>
</div>
<div className="satisfactioncontainer">
<div className="leftalignment"><FontAwesomeIcon icon={faBalanceScale}/></div>
<p className="satisfactionstat">Satisf %</p>
</div>
</div>
<Challengebox
key={challenge._id}
id={challenge._id}
title={challenge.title}
description={challenge.description}
/>
<button className="deletebutton" onClick={this.onDelete}>
Delete
</button>
</div>
))
}
</div>
</div>
</DefaultLayout>
)
}
}
export default Allchallenges
It looks like you forgot to pass the challenge id to the handler.
<button className="deletebutton" onClick={() => this.onDelete(challenge._id)}>
Delete
</button>
onClick accepts an event object as its parameter. You have assumed it will be the id of the challenge you want to delete, so you're trying to add it to the query string. This is why you're getting the object error.
Instead, pass onClick an anonymous function and call onDelete with the id:
<button className="deletebutton" onClick={() => this.onDelete(challenge._id)}>
Since you don't need the event, we don't include it in the function, and instead call onDelete with the id from the current mapped element.

React Todo Checkbox Styles all listed items at once

I'm trying to create a todo-list with React. I am able to display the list in the display area and also able to remove the items. But when I click on one checkbox, all the checkboxes are selected and the class is applied to all the list items. I'm not sure what is it that I am doing wrong.
I tried to use the same logic as I did with the deleted item(that's using the filter), but it doesn't work. I looked other cases here but they are mostly about how to do it with jQuery.
Here is the working example of my problem.
This is the List class
class List extends Component {
state={
check: false,
strike: 'none'
}
onCheck(item){
this.setState({check: !this.state.check})
if (this.state.strike === 'none'){
this.setState({strike: 'line-through'})
} else {
this.setState({strike: 'none'})
}
}
render() {
const strike = {
textDecoration: this.state.strike,
}
return (
<ul className='list-style'>
{ this.props.items.map((item, index) =>
<li key={index}>
<div className="outer-div">
<div className="item-checkbox">
<input type="checkbox" checked={this.state.check}
onChange={() => this.onCheck(item)} />
</div>
<div className="item-text">
<span style= {strike}> {item} </span>
</div>
<div className="item-remove-div">
<button className="item-remove" onClick={() => this.props.onDeleteList(index)}>
Remove
</button>
</div>
</div>
<br />
</li>
)}
</ul>
)}
}
export default List;
And this is the Main Class:
class Main extends Component {
state = {
items: [],
term : "",
}
onChange(event){
this.setState({ term: event });
}
onDelete= (item) =>{
// this.setState ({
// items: this.state.items.filter((i) => i.index !== item.index)
// })
this.state.items.splice(item, 1);
this.setState({items: this.state.items});
}
onSubmit= (event) => {
event.preventDefault();
if (this.state.term.length > 0){
this.setState({
term: '',
items: [...this.state.items, this.state.term]
});
}
}
render() {
return (
<div className="center">
<h1 className="header" > TODO-LIST </h1>
<div className='mainCenter'>
<form className="App" onSubmit={this.onSubmit}>
<input placeholder="add task" value={this.state.term} onChange={(e) => this.onChange(e.target.value)}
className="inputField"/>
<button>Add to the List</button>
</form>
<List items={this.state.items} onDeleteList={this.onDelete}/>
<div className="footer-outer">
<span className="footer"> Number of completed items in an array: {this.state.items.length} </span>
</div>
</div>
</div>
);
}
}
I edited your SlackBlitz. Now you can properly add new todos, check individuals tasks (toggle checked on todo click) and see correct checked counter in the footer.
Check todo-list-react demo.
import React, { Component } from 'react';
import TodoList from './List';
import './style.css';
class Main extends Component {
constructor() {
super();
this.state = {
items: [],
term: ''
};
}
handleChange = event => {
this.setState({ term: event.target.value });
}
handleItemClick = ({ value, checked }) => {
this.setState({
items: this.state.items.map(item => item.value === value ? { value, checked: !checked } : item)
});
}
onSubmit = event => {
event.preventDefault();
if (this.state.term.length > 0) {
this.setState({
term: '',
items: [...this.state.items, { value: this.state.term, checked: false }]
});
}
}
handleDelete = index => {
console.info('todo: remove todo at index', index);
// deletion logic... keep in mind that using index as key properties on jsx could breaks the correct functioning of this component.
}
render() {
return (
<div className="center">
<h1 className="header" > TODO-LIST </h1>
<div className='mainCenter'>
<form className="App" onSubmit={this.onSubmit}>
<input placeholder="add task" value={this.state.term} onChange={this.handleChange}
className="inputField"/>
<button>Add to the List</button>
</form>
<TodoList
onTodoClick={this.handleItemClick}
onDelete={this.handleDelete}
todos={this.state.items}
/>
<div className="footer-outer">
<span className="footer">
Number of completed items in an array:
{this.state.items.filter(item => item.checked).length}
</span>
</div>
</div>
</div>
);
}
}
export default Main
import React, { Component } from 'react';
import './style.css';
class List extends Component {
render() {
const { todos, onTodoClick, onDelete } = this.props;
return (
<ul className='list-style'>
{
todos.map((item, index) =>
<li key={index}>
<div className="outer-div">
<div className="item-checkbox">
<input type="checkbox" checked={item.checked}
onChange={() => onTodoClick(item)} />
</div>
<div className="item-text">
<span style={checkboxStyle(item.checked)}>{item.value}</span>
</div>
<div className="item-remove-div">
<button className="item-remove"
onClick={() => onDelete(index)}>
Remove
</button>
</div>
</div>
<br />
</li>
)}
</ul>
)}
}
function checkboxStyle(checked) {
return {
textDecoration: checked? 'line-through' : 'none',
};
}
export default List;
In addition to this answer, I recommend you to consider to add an unique key property to each jsx-element differen from the array index. Current implementation has no problem, but once you start deleting todo items probably display wrong data.
Read List and Keys from React docs and this article on Medium which covers possible error when using indixes as keys.
The reason that all your list items are being 'striked' is because you have only one state reserved for all the items in the list. You need to have the checked or strike state for each item in the list. However, as I view your comments, I realize that you already know that.
You have several other inconsistencies in the code:
onDelete= (item) =>{
this.state.items.splice(item, 1);
this.setState({items: this.state.items});
}
Making direct changes to the state like that might cause unwanted errors and unusual behavior. A better way to do it is to:
onDelete = (item) => {
const items = this.state.items.slice();
items.splice(item, 1);
this.setState({
items: items,
});
}
For more info refer to this article:
https://medium.com/pro-react/a-brief-talk-about-immutability-and-react-s-helpers-70919ab8ae7c

Categories