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}
/>
))
};
Related
I loop through an array of elements:
this.props.connections.map((connection) => (
For each element in this array a card is created. In this card, I implemented a toogle button:
<div id="bookmarkIcon">
{this.state.available ? (
<Tab onClick={this.handleChange} icon={<StarBorderIcon/>}
aria-label="StarBorder"/>) : <Tab onClick={this.handleChange} icon={<StarIcon/>}
aria-label="StarIcon"/>}
</div>
The handle change method changes the value of available to false. The problem is that then I change the state and therefore, ever icon toggles, but I just want to toggle the icon I clicked on. How can I achieve this?
You can create an object which keeps the state as keys.
Here is a working example:
hidden will look something like this {0: true, 1: true, 2: false}
so we can update the corresponding items by their index.
https://codesandbox.io/s/intelligent-black-83cqg?file=/src/App.js:0-577
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [hidden, setHidden] = useState({});
const list = ["aa", "bb", "cc", "dd"];
const toggleHide = index => {
setHidden({ ...hidden, [index]: !hidden[index] });
};
return (
<div className="App">
{list.map((item, index) => (
<div>
{!!hidden[index] && <span>[HIDDEN]</span>}
{!hidden[index] && <span>[VISIBLE]</span>}
{item} <span onClick={e => toggleHide(index)}>x</span>
</div>
))}
</div>
);
}
Class-Based Component
class PrivacyPolicyDetails extends Component {
constructor(props) {
super(props);
this.state ={
resultData:[],
error:false ,
active: false,
activeIdList:[]
}
this.toggleClass.bind(this);
}
componentDidMount() {
setting.getQuestionAnswerByType('privacy-policy')
.then(res =>{
if(res.data.questionAnswerList.length > 0){
this.setState({
resultData: res.data.questionAnswerList,
})
}else{
this.setState({
error:true
});
}
}
);
}
toggleClass(id) {
const currentState = this.state.active;
this.setState({ active: !currentState});
if(this.state.activeIdList.find(element => element == id)){
this.state.activeIdList = this.state.activeIdList.filter(item => item !== id);
}else{
this.state.activeIdList.push(id);
}
}
render() {
const { product, currency } = this.props;
const {resultData,error,activeIdList} = this.state;
return (
<div>
<h1>Privacy Policy</h1>
{resultData && resultData.length > 0 ? resultData.map(each_policy =>
<div className="item">
<div className="question"
onClick={() => this.toggleClass(each_policy.question_answer_repository_id)}
>
{each_policy.question}
</div>
<p className={(activeIdList.find(element => element == each_policy.question_answer_repository_id))? "active-div" :"hide"}>
<div className="answer">{each_policy.answer}</div>
</p>
</div>
):''}
</div>
);
}
}
const mapStateToProps = (state) => {
return state.setting;
};
export default connect(mapStateToProps)(PrivacyPolicyDetails);
css
.hide{
display: none;
overflow:hidden;
}
.active-div{
display: block;
overflow:hidden;
}
Make the card into its own component and implement the state of the toggle inside of that component. In your parent component just map each card into one of these components. Each card will have its own toggle which uses the state of the card to determine how it should display.
I am making a dashboard component which displays rendered previews and code for HTML snippets. Inside of the dashboard component I am mapping the array of snippets using .map. Each mapped snippet is going to have a delete function (already built) and an update function.
For the update function to work each snippet has it's own child modal component. I need to pass the ID of the snippet to the modal component where I can combine the ID with the new content before updating the database and state.
However, I'm making a mistake somewhere as I pass the ID as props to the modal.
.map used inside of my Dashboard.js Dashboard class component.
{this.state.snippets.map(snippet => (
<>
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p> //THIS WORKS
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button type="button" onClick={this.showModal}>
Open
</button>
</div>
</div>
</div>
<Modal
sid={snippet._id} //PASS ID HERE
show={this.state.show}
handleClose={this.hideModal}
></Modal>
</>
))}
This renders the snippets below (3 snippet pods, with their database ID included).
The open button opens the modal (Modal.js) below.
import React, { Component } from 'react'
import api from '../api'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
handleInputChange = event => {
this.setState({
[event.target.name]: event.target.value,
})
}
handleClick = id => event => {
event.preventDefault()
console.log(id)
}
render() {
const { sid, show, handleClose } = this.props
console.log(sid)
const showHideClassName = show ? 'modal display-flex' : 'modal display-none'
return (
<div id="Modal" className={showHideClassName}>
<div id="modal-main">
<h4>Edit snippet {sid}</h4>
<form>
Name:{' '}
<input
type="text"
value={this.state.name}
name="name"
onChange={this.handleInputChange}
/>{' '}
<br />
Details:{' '}
<input
type="text"
value={this.state.details}
name="details"
onChange={this.handleInputChange}
/>{' '}
<br />
Content:{' '}
<textarea
value={this.state.content}
name="content"
cols="30"
rows="10"
onChange={this.handleInputChange}
/>{' '}
<br />
<button onClick={this.handleClick(sid)}>TEST ME</button>
</form>
<button onClick={handleClose}>Close</button>
{this.state.message && (
<div className="info">{this.state.message}</div>
)}
</div>
</div>
)
}
}
The console.log just under the render actually pastes the correct 3 ID's the console.
However, calling the ID (sid) within the Modal.js return will only show the last snippet ID, no matter which Modal I open. The same goes for pushing that ID to the handleClick function where I intend to combine the ID with an update package.
Solution below as initiated by HMR in the comments.
The problem was all the modals were showing and just the last one was visible.
Fixed by moving the modal out of the .map and instead updating the ID from within the .map to the state and passing the state ID to a new nested component within the modal.
Also switched to using dynamic CSS to show and hide the modal based on the state.
Dashboard.jsx
export default class Snippets extends Component {
constructor(props) {
super(props)
this.showModal = React.createRef()
this.state = {
snippets: [],
show: false,
sid: '',
}
}
handleDelete = id => event => {
event.preventDefault()
api
.deleteSnippet(id)
.then(result => {
console.log('DATA DELETED')
api.getSnippets().then(result => {
this.setState({ snippets: result })
console.log('CLIENT UPDATED')
})
})
.catch(err => this.setState({ message: err.toString() }))
}
handleModal = id => {
this.setState({ sid: id })
this.showModal.current.showModal()
}
//<div id="preview">{ReactHtmlParser(snippet.content)}</div>
render() {
return (
<>
<Modal ref={this.showModal} handleClose={this.hideModal}>
<ModalUpdate sid={this.state.sid} />
</Modal>
<div className="Dashboard">
<div className="wrapper">
<div className="container">
<div className="holder">
<div className="content">
<div className="content__body">
<h3>Dashboard</h3>
</div>
</div>
</div>
<div className="break"></div>
{this.state.snippets.map(snippet => (
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p>
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button
type="button"
onClick={() => this.handleModal(snippet._id)}
>
Open
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</>
)
}
Modal.jsx
import React, { Component } from 'react'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
show: false,
}
}
showModal = () => {
this.setState({ show: true })
}
hideModal = () => {
this.setState({ show: false })
}
render() {
return (
<div
id="Modal"
style={{ display: this.state.show === true ? 'flex' : 'none' }}
>
<div id="modal-main">
<h4>Edit snippet </h4>
{this.props.children}
<button onClick={() => this.hideModal()}>Close</button>
</div>
</div>
)
}
}
ModalUpdate.jsx
import React, { Component } from 'react'
export default class ModalUpdate extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
// handleInputChange = event => {
// this.setState({
// [event.target.name]: event.target.value,
// })
// }
// handleClick = id => event => {
// event.preventDefault()
// console.log(id)
// }
render() {
return <h4>ID = {this.props.sid}</h4>
}
}
I am not sure about the handleDelete function,. but replacing the line should solve the issue probably
<button onClick={() => this.handleDelete(snippet._id)}>
One potential issue is the this.handleDelete(snippet._id) will fire immediately rather than onClick, so you will need to add an anonymous function in the event listener:
() => this.handleDelete(snippet._id)
instead of
this.handleDelete(snippet._id)
Iam new to React and I'm trying to interact with the swapi API.
I want to get the list of films (movie titles list) and when I click on a title to show the opening_crawl from the json object.
I managed to get the film titles in an array. I don't know how to proceed from here.
Here is my code:
class StarWarsApp extends React.Component {
render() {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
class Movies extends React.Component {
constructor(props) {
super(props);
this.handleMovies = this.handleMovies.bind(this);
this.state = {
movies: []
};
this.handleMovies();
}
handleMovies() {
fetch("https://swapi.co/api/films")
.then(results => {
return results.json();
})
.then(data => {
console.log(data);
let movies = data.results.map(movie => {
return <div key={movie.episode_id}>{movie.title}</div>;
});
this.setState(() => {
return {
movies: movies
};
});
});
}
render() {
return (
<div>
<h1>Episodes</h1>
<div>{this.state.movies}</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("app"));
To iterate over movies add this in render method:
render(){
return (
<div>
<h1>Episodes</h1>
{
this.state.movies.map((movie, i) => {
return (
<div className="movie" onClick={this.handleClick} key={i}>{movie.title}
<div className="opening">{movie.opening_crawl}</div>
</div>
);
})
}
</div>
);
}
Add this method to your Movies component to add active class on click to DIV with "movie" className:
handleClick = event => {
event.currentTarget.classList.toggle('active');
}
Include this css to your project:
.movie .opening {
display: none;
}
.active .opening {
display: block
}
After fetching the data, just keep it in your state then use the pieces in your components or JSX. Don't return some JSX from your handleMovies method, just do the setState part there. Also, I suggest using a life-cycle method (or hooks API maybe if you use a functional component) to trigger the fetching. By the way, don't use class components unless you need a state or life-cycle methods.
After that, you can render your titles in your render method by mapping the movies state. Also, you can have a place for your opening_crawls part and render it with a conditional operator. This condition changes with a click. To do that you have an extra state property and keep the movie ids there. With the click, you can set the id value to true and show the crawls.
Here is a simple working example.
const StarWarsApp = () => {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
const Header = ({ title }) => (
<div>
<h1>{title}</h1>
</div>
);
class Movies extends React.Component {
state = {
movies: [],
showCrawl: {}
};
componentDidMount() {
this.handleMovies();
}
handleMovies = () =>
fetch("https://swapi.co/api/films")
.then(results => results.json())
.then(data => this.setState({ movies: data.results }));
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
render() {
return (
<div>
<h1>Episodes</h1>
<div>
{this.state.movies.map(movie => (
<div
key={movie.episode_id}
id={movie.episode_id}
onClick={this.handleCrawl}
>
{movie.title}
{this.state.showCrawl[movie.episode_id] && (
<div style={{ border: "1px black solid" }}>
{movie.opening_crawl}
</div>
)}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("root"));
<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>
<div id="root"></div>
I am using id on the target div to get it back from the event object. I don't like this method too much but for the sake of clarity, I used this. You can refactor it and create another component may be, then you can pass the epoisde_id there and handle the setState part. Or you can use a data attribute instead of id.
I am new to both coding as well as React.js, so any assistance in learning what I am doing incorrectly is greatly appreciated! I am creating multiple cards on a page with riddles where the answer is hidden via css. I am using an onClick function ("toggleAnswer") to toggle the state of each answer to change the className so that the answer will either be visible or hidden. Currently, the onClick event is changing the state for all the answers. I realize this is because my code is not targeting a particular element, but I am unsure how this can be done. How can this be achieved? My code is currently like this:
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
isHidden: true
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e) {
this.setState({ isHidden: !this.state.isHidden });
}
componentWillMount() {
this.getPageData();
}
render() {
const answerClass = this.state.isHidden ? "answer-hide" : "answer";
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={answerClass}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={props.onClick}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
You'd have to keep track of each answer that has been shown in state (in an array or something).
First
Send the index of the answer up in the onclick function. In that function, check if it exists in the "shownAnswers" array and either add or remove it.
onClick={e => props.onClick(e, props.id)}
and
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({
shownAnswers: this.state.shownAnswers.filter(val => val !== index)
});
} else {
this.setState({
shownAnswers: this.state.shownAnswers.concat(index)
});
}
}
Then
When you're passing the class name down to the child component, check if its index is in the "shownAnswers" array to decide which class name to pass.
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
Building off your example, it could look something like this (untested):
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
shownAnswers: []
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({ shownAnswers: this.state.shownAnswers.filter(val => val !== index) });
} else {
this.setState({ shownAnswers: this.state.shownAnswers.concat(index) });
}
}
componentWillMount() {
this.getPageData();
}
render() {
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={e => props.onClick(e, props.id)}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
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