I'm a beginner with React creating a chatbot that returns a set of button choices for every action. I'm using Socket.io on the server end and React on the front.
I want every button to have an OnClick function which I'm unable to implement
Here's my data format that server returns
var data = {
buttonShow: true,
buttons: [
{
text: 'I DID NAAAAHT'
},
{
text: 'OH HAI MARK'
}
],
message: {
author: "Mark",
time: '12:00 PM',
message: "Did you hit Lisa?"
}
};
Here is my Chat Window component class
import React from 'react';
import ReactDOM from 'react-dom';
import { Link } from 'react-router';
import io from "socket.io-client";
class ChatWindow extends React.Component{
constructor(props){
super(props);
this.state = {
email: '',
message: '',
time: '',
buttonShow: false,
buttons: [],
messages: [],
buttonSelection: ''
}
this.socket = io('http://localhost:8080')
this.socket.on('RECEIVE_MESSAGE', function(data){
addMessage(data);
});
const addMessage = data => {
console.log(data);
this.setState({messages: [...this.state.messages, data.message]});
this.setState({buttonShow: data.buttonShow});
this.setState({buttons: data.buttons});
console.log(this.state);
//if(this.state.buttonShow==false){}
};
this.sendButtonSelection = e => {
this.setState({buttonSelection: e.target.value});
console.log(e.target.value);
};
};
render(){
return(
<div>
<div className="panel-body">
{this.state.messages.map(message => {
return(
<ul className="chat">
<li className="">
<div className="chat-body clearfix">
<div className="header">
<strong className="primary-font">{message.author}</strong> <small className="pull-right text-muted">
<span className="glyphicon glyphicon-time"></span>{message.time}</small>
</div>
<p>{message.message}</p>
</div>
</li>
</ul>
)
})}
</div>
<div class="panel-footer">
{<ChatInput myDataProp = {this.state.buttonShow} myButtonProp = {this.state.buttons} onClickProp = {this.sendButtonSelection}/>}
{}
</div>
</div>
);
}
};
class ChatInput extends React.Component {
render(){
if(this.props.myDataProp == false) {
return (
<div className="input-group">
<input id="btn-input" type="text" className="form-control input-sm" placeholder="Type your message here..." />
<span className="input-group-btn">
<button className="btn btn-warning btn-sm" id="btn-chat">
Send</button>
</span>
</div>
)
} else {
return (
<div>
{this.props.myButtonProp.map(function(item, i){
return(
<Button buttonProp = {item.text} buttonOnClick = {this.props.onClickProp}/>
)
})}
</div>
)
}
}
}
class Button extends React.Component {
render(){
return(
<div>
<button type="button" className="btn btn-default btn-round" value={this.props.buttonProp} onClick={this.props.buttonOnClick}>{this.props.buttonProp}</button>
</div>
)
}
}
module.exports = ChatWindow;
However, when this component is rendered, I see this error -
Cannot read property 'props' of undefined
This is killing me. I've tried everything possible.
You could pass thisArg to map Docs.
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
{this.props.myButtonProp.map(function(item, i){
return(
<Button buttonProp = {item.text} buttonOnClick = {this.props.onClickProp}/>
)
}, this)}
Or use arrow function instead.
{this.props.myButtonProp.map((item, i) => (
<Button buttonProp = {item.text} buttonOnClick = {this.props.onClickProp}/>)
)}
Related
I have listing view working correctly and I want to pass data to update view by Id - in URL Id is passed correctly, but without data saved to this Id. In console log id is tagged as undefined
UpdateCar.jsx
import React, { Component } from 'react';
import CarServices from '../../Services/CarServices';
class UpdateCar extends Component {
constructor(props) {
super(props)
this.state = {
carId: this.props.match.id,
brand: '',
color: ''
}
this.changeBrandHandler = this.changeBrandHandler.bind(this);
this.changeColorHandler = this.changeColorHandler.bind(this);
this.getCarId = this.getCarId.bind(this);
this.updateCar = this.updateCar.bind(this);
}
componentDidMount() {
CarServices.getCarById(this.state.carId).then((res) => {
let car = res.data;
this.setState({
brand: car.brand,
color: car.color
});
});
}
changeBrandHandler = (event) => {
this.setState({ brand: event.target.value });
}
changeColorHandler = (event) => {
this.setState({ color: event.target.value });
}
updateCar = (e) => {
e.preventDefault();
let car = { brand: this.state.brand, color: this.state.color };
console.log('test: ' + JSON.stringify(car));
console.log('id => ' + JSON.stringify(car.carId));
}
cancel() {
this.props.history.push('/showCars');
}
render() {
return (
<div>
<div className='container'>
<div className='row'>
<div className='card col-md-6 offset-md-3 offset-md-3'>
<h3 className='text-center'> Edit car </h3>
<div className='cardBody'>
<form>
<div className='form-group'>
<label> Brand: </label>
<input placeholder="brand" name="brand" className="form-control"
value={this.state.brand} onChange={this.changeBrandHandler} />
<label> Color: </label>
<input placeholder="color" name="color" className="form-control"
value={this.state.color} onChange={this.changeColorHandler} />
</div>
<button className="btn btn-success" onClick={this.updateCar}>Save</button>
<button className="btn btn-danger" onClick={this.cancel.bind(this)} style={{ marginLeft: "10px" }}>Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default UpdateCar;
CarServices.js
When I hardcode id in url it works correclty - I don't know why I don't have any data from database in placeholders
import axios from 'axios';
const CAR_API_BASE_URI = "http://localhost:8080/car"
class CarServices{
getCars(){
return axios.get(CAR_API_BASE_URI);
}
addCar(car){
return axios.post(CAR_API_BASE_URI, car);
}
getCarById(id){
return axios.get(CAR_API_BASE_URI + '/' + id);
//return axios.get("http://localhost:8080/car/2"); - correclty view data from database saved with id:2
}
}
export default new CarServices();
ListCar.jsx
import React, { Component } from 'react';
import CarServices from '../../Services/CarServices';
class ListCar extends Component {
constructor(props){
super(props)
this.state = {
cars: []
}
this.addCar = this.addCar.bind(this);
this.editCar = this.editCar.bind(this);
}
addCar(){
this.props.history.push('/addCar');
}
editCar(id){
this.props.history.push(`/editCar/${id}`);
}
componentDidMount(){
CarServices.getCars().then((res)=>{
this.setState({ cars: res.data})
})
}
render() {
return (
<div>
<h2 className='text-center'>Car list </h2>
<div className='row'>
<button className='btn btn-primary' onClick={this.addCar} style={{marginLeft: "15px"}} >Add car</button>
</div>
<div className='row'></div>
<table className='table table-striped table-bordered'>
<thead>
<tr>
<th className='text-center'>Id</th>
<th className='text-center'>brand</th>
<th className='text-center'>color</th>
<th className='text-center'>action</th>
</tr>
</thead>
<tbody>
{
this.state.cars.map(
car =>
<tr key = {car.carId}>
<td className='text-center'>{car.carId}</td>
<td className='text-center'>{car.brand}</td>
<td className='text-center'>{car.color}</td>
<td className='text-center'>
<button onClick ={ () => this.editCar(car.carId)} className="btn btn-info">Update </button>
<button style={{marginLeft: "10px"}} className="btn btn-danger">Delete </button>
<button style={{marginLeft: "10px"}} className="btn btn-info">View </button>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
export default ListCar;
Assuming the UpdateCar component is correctly rendered on a Route and receives the route props, the issue is likely that this.props.match.id won't necessarily be defined in the constructor when the component is mounting.
If you must reference props in the constructor then reference the passed props argument.
constructor(props) { // <-- reference this props
super(props)
this.state = {
carId: props.match.id, // reference props arg
brand: '',
color: ''
}
...
}
It's better to reference props directly though as it's considered anti-pattern in React to store props into local state.
componentDidMount() {
const { match } = this.props;
if (match.id) {
CarServices.getCarById(match.id)
.then((res) => {
const { brand, color } = res.data;
this.setState({ brand, color });
})
.catch(error => {
// catch and handle any Promise rejections or thrown errors
});
}
}
Don't forget to also handle the id route path parameter changing while the UpdateCar component is mounted.
componentDidUpdate(prevProps) {
const { match } = this.props;
if (prevProps.match.id !== match.id) {
CarServices.getCarById(match.id)
.then((res) => {
const { brand, color } = res.data;
this.setState({ brand, color });
})
.catch(error => {
// catch and handle any Promise rejections or thrown errors
});
}
}
I am writing todo app. There are main files in my directory now:
App (rendering main page with header and buttons)
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { triggerText: 'Create a task' };
}
propTypes = {
triggerText: PropTypes.string.isRequired,
handleSubmit: PropTypes.object.isRequired,
};
render() {
const { triggerText } = this.state;
const { handleSubmit } = this.props;
return (
<div className="App">
<header className="App-header">
<h1>To Do List</h1>
<div id="tasksList">
<span className="tasks active">Tasks</span>
</div>
<div id="categoriesList">
<span className="categories">Categories</span>
</div>
<div>
<Container triggerText={triggerText} onSubmit={handleSubmit} /> // creates modal dialog and uses TodoForm
</div>
</header>
<div id="container" className="container">
<TodoBox tasks={[]}/>
</div>
</div>
);
}
}
TodoForm (create a form)
export default class TodoForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: '', tasks: [] };
}
propTypes = {
handleSubmit: PropTypes.object.isRequired,
}
handleRemove = (currentTaskId) => (e) => {
e.preventDefault();
const { tasks } = this.state;
this.setState({ tasks: tasks.filter(({ id }) => id !== currentTaskId) });
};
handleChange = (e) => {
e.preventDefault();
this.setState({ value: e.target.value });
}
handleSubmit = (e) => {
e.preventDefault();
const { value, tasks } = this.state;
const newTask = { id: uniqueId(), text: value };
this.setState({ value: '', tasks: [newTask, ...tasks] });
}
render() {
const { value } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="text"><strong>Create a task</strong></label>
<input
type="text"
onChange={this.handleChange}
value={value}
required
className="form-control"
id="text"
placeholder="I am going..."
/>
</div>
<div className="form-group">
<button type="submit" className="form-control btn btn-primary">Add</button>
</div>
</form>
);
}
}
TodoBox (generating list of tasks)
class Item extends React.Component {
propTypes = {
onRemove: PropTypes.object.isRequired,
task: PropTypes.string.isRequired,
};
render() {
const { task, onRemove } = this.props;
return (
<div className="row">
<div>
<button type="button" className="btn btn-primary" onClick={onRemove}>-</button>
</div>
<div className="col-10">{task.text}</div>
</div>
);
}
}
export default class TodoBox extends React.Component {
constructor(props) {
super(props);
}
propTypes = {
tasks: PropTypes.string.isRequired,
}
render() {
const { tasks } = this.props;
return (
<div className="item">
{tasks.map((task) => (
<div key={task.id}>
<Item task={task} onRemove={this.handleRemove} />
<hr />
</div>
))}
</div>
);
}
}
And the question is: how I can pass the state from TodoForm to TodoBox in App (it is initialize as an empty array now). I want to output tasks at the bottom of the same page in container after header element.
You can create a function (addTodo) in App component and pass it down to the TodoForm component. In TodoForm component you can invoke the addTodo function from props and send the todoValue as arguments props.addTodo(todoValue). In addTodo function in App component you can update the todoValue to state. Once you update the state it will re-render the App component, then the TodoBox component will call with the updated todoValue value.
Note: But it is not best practice. The best practice is to use React Context
I have one question. I created contact form in react and I want to collapsed only single contact, which was clicked. Toggle is method which should collapse it. And colapse is state. My problem is that when I click it affect all contact and all are collapsed. How can I improve it?
ContactBook.js
import React, { Component } from "react";
import Contact from "../Contact/Contact";
import "./ContactBook.css";
class ContactBook extends Component{
constructor(props){
super(props);
this.state = {
colapse :true,
contacts: [
{
id: 1,
name: 'Propulsion Academy',
address: 'Zurich',
avatar: 'propulsion-academy-logo.png'
},
{
id: 2,
name: 'Propulsion Academy',
address: 'Luzern',
avatar: 'propulsion-academy-logo.png'
},
{
id: 3,
name: 'Propulsion Academy',
address: 'Munich',
avatar: 'propulsion-academy-logo.png'
},
],
};
}
toggle=()=>{
const doesShow = this.state.colapse;
this.setState({colapse: !doesShow});
}
deleteContact=(contactIndex)=>{
//with slice method we create copy of an array
const contacts =this.state.contacts.slice();
contacts.splice(contactIndex, 1);
this.setState({contacts: contacts})
}
//get name from input
addName = e =>{
this.setState({
name: e.target.value,
})
}
//get address from input
addAddress = e =>{
this.setState({
address: e.target.value,
})
}
//update state on button click
handleSubmit = (e) =>{
e.preventDefault()
if(this.state.name && this.state.address) {
this.setState(state =>{
const newContact = {
id: Math.max(...state.contacts.map(c => c.id))+1,
name: this.state.name,
address: this.state.address,
}
return{
contacts:[...state.contacts, newContact]
}
})
}
}
render() {
return (
<div className="contactBook">
<form className ="addContact" >
<p>New Contact</p>
<label id="name"><p>Name</p><input type='text' id="name" onChange={this.addName}/></label>
<label id="address"><p>Address:</p><input type='text' id="address" onChange={this.addAddress} /></label>
<input type='file' name='file' />
<button type='submit' onClick= {this.handleSubmit}>SUBMIT</button>
</form>
<div className="contacts">
{this.state.contacts.map((contact, index) =>
< Contact key={contact.id} contact={contact} delete={()=>this.deleteContact(index)} colapse={this.state.colapse} toggle={this.toggle}/>)
}
</div>
</div>
);
}
};
export default ContactBook;
Contact.js
import React from "react";
import "./Contact.css";
import avatar from '../assets/user.png'
const Contact = (props) =>{
return (
<div className = "col" >
<img src={avatar} alt="avatar" onClick={props.toggle}/>
{props.colapse === true ?
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div> : null
}
</div>
)
};
export default Contact;
I recommend to you to move the collapse and his method to the Contact component it self like this :
const Contact = (props) =>{
[collapse,setCollapse] = useState(true)
return (
<div className = "col" >
<img src={avatar} alt="avatar" onClick{()=>setCollape(prev=>!prev)}/>
{collapse === true ?
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div> : null
}
</div>
)
};
In this component I created a state that will manage the collapse for each of the component the render in the map.
further more, the prev give you the last value you submit and it's best practice to use the prev instead of just setCollapse(!collapse)
You have one function for all contacts, and since you use .map() they will all behave the same, since toggling one toggles the state which is used to render all individual contacts. The solution would be to pass the selected contact in your state so your app actually knows which one is to be rendered! Hopefully that makes sense!
Good luck and let us know how things work out!
This is it:
ContactBook.js
import React, { Component } from "react";
import Contact from "./Contact";
// import "./ContactBook.css";
class ContactBook extends Component {
constructor(props) {
super(props);
this.state = {
contacts: [
{
id: 1,
name: "Propulsion Academy",
address: "Zurich",
avatar: "propulsion-academy-logo.png",
colapse: true
},
{
id: 2,
name: "Propulsion Academy",
address: "Luzern",
avatar: "propulsion-academy-logo.png",
colapse: true
},
{
id: 3,
name: "Propulsion Academy",
address: "Munich",
avatar: "propulsion-academy-logo.png",
colapse: true
}
]
};
}
// toggle = () => {
// const doesShow = this.state.colapse;
// this.setState({ colapse: !doesShow });
// };
deleteContact = contactIndex => {
//with slice method we create copy of an array
const contacts = this.state.contacts.slice();
contacts.splice(contactIndex, 1);
this.setState({ contacts: contacts });
};
togglecontact = contactIndex => {
let contacts = this.state.contacts.slice();
contacts[contactIndex].colapse = !contacts[contactIndex].colapse;
this.setState({ contacts: contacts });
};
//get name from input
addName = e => {
this.setState({
name: e.target.value
});
};
//get address from input
addAddress = e => {
this.setState({
address: e.target.value
});
};
//update state on button click
handleSubmit = e => {
e.preventDefault();
if (this.state.name && this.state.address) {
this.setState(state => {
const newContact = {
id: Math.max(...state.contacts.map(c => c.id)) + 1,
name: this.state.name,
address: this.state.address
};
return {
contacts: [...state.contacts, newContact]
};
});
}
};
render() {
return (
<div className="contactBook">
<form className="addContact">
<p>New Contact</p>
<label id="name">
<p>Name</p>
<input type="text" id="name" onChange={this.addName} />
</label>
<label id="address">
<p>Address:</p>
<input type="text" id="address" onChange={this.addAddress} />
</label>
<input type="file" name="file" />
<button type="submit" onClick={this.handleSubmit}>
SUBMIT
</button>
</form>
<div className="contacts">
{this.state.contacts.map((contact, index) => (
<Contact
key={contact.id}
contact={contact}
togglecontact={() => this.togglecontact(index)}
delete={() => this.deleteContact(index)}
colapse={this.state.colapse}
toggle={this.toggle}
/>
))}
</div>
</div>
);
}
}
export default ContactBook;
contactbook.js
import React from "react";
// import "./Contact.css";
const Contact = props => {
let buffer;
props.contact.colapse === true
? (buffer = (
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div>
))
: null;
return (
<div className="col">
<img
onClick={props.togglecontact}
src="data:image/jpeg;base64,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"
/>
{buffer}
</div>
);
};
export default Contact;
can check it at:https://stackblitz.com/edit/react-mrvrr1?file=src%2FContact.js
only Toggling single element.
I'm trying to access a parent method from a child to show a modal on screen and I'm getting the error: This.props.toggleModal is not a function. I'm passing the method down to the child so it can be called and using the correct state (I think). The button does call it's own method which in turn calls the parent. The modal component sits inside App.js.
App.js
class App extends Component {
constructor() {
super()
this.state = {
isOpen: false
}
}
toggleModal = () => {
this.setState({
isOpen: !this.state.isOpen
});
console.log('Open');
}
render() {
return (
<div className="App">
<Modal toggleModal={this.toggleModal} show={this.state.isOpen}
onClose={this.toggleModal}>
Here's some content for the modal
</Modal>
<div className="container">
<Header/>
<main>
<Route path="/users"
children={({ match, ...rest }) => (
<TransitionGroup component={firstChild}>
{match && <UserList {...rest} />}
</TransitionGroup>
)}/>
...
</main>
<Footer />
</div>
</div>
);
}
}
SearchBar.js - (located inside the user page)
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = {
type: this.props.type,
value: ''
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.type !== this.props.type) {
this.setState({ type: nextProps.type });
}
};
handleClick = (e) => {
e.preventDefault();
console.log("Clicked!!!");
this.props.toggleModal();
};
handleChange = e => {
console.log(this.state.type);
this.setState({ value: e.target.value });
};
render () {
const isUser = this.state.type;
let rightContent = null;
if (isUser === "tour" || isUser === "venue") {
rightContent =
<div className="column">
<div className="float-right"><button className="add-new" onClick={this.handleClick}>Add New</button></div>
</div>
} else {
rightContent =
<div className="column">
<div className="float-right">
<div className="results-block">
<b>0</b>/<small>292</small>
</div>
</div>
</div>
}
return (
<div className="row main-search">
<div className="column">
<form action="">
<fieldset>
<label htmlFor="search">
<input type="text"
placeholder="Start typing..."
id="search-box"
onChange={this.handleChange}
value={this.state.value} />
</label>
</fieldset>
</form>
</div>
{rightContent}
</div>
)
}
}
export default SearchBar;
Check IF you getting toggleModal as props in your User Page Compoenent. If yes then pass it explicitly like to SearchBar
<SearchBar toggleModal = {this.props.toggleModal } /> // plus your rest of the props
You have to bind toggleModal with this in constructor so that use it as this.toggleModal.
EX.
this.toggleModal = this.toggleModal.bind(this);
check this ReactJs doc for more info.
https://reactjs.org/docs/handling-events.html
I have written the start of a todo list in reactjs and was looking to improve the functionality of how the todos are added to the state. Currently I am concat(ting) the value in put to an array which is in the state, then splicing off the selected li element. It seems to be a bit buggy when you add the first todo. Should i be using reacts immutability helpers to acheive this? Seems overkill to add another thing that can be acheived in plain js.
//Input component
const Input = props => {
return (
<div className="form-group">
<input
className="form-control"
value={props.value}
onChange={props.update}
type="text"
/>
<button className="btn btn-default" onClick={props.handleClick}>
Add Todo
</button>
</div>
);
};
//display list of todos
const Displaytodo = (props) => {
const todolist = props.todo;
const listItems = todolist.map((todo, index) =>
<li
className={
props.highlight ? 'list-unstyled todoItem highlight' : 'list-unstyled todoItem '
}
key={index}>
{todo}
<div
onClick={props.removeTodo.bind(this, index)}
className="removeTodo">
<i className="fa fa-trash" />
</div>
<div onClick={props.changeHighlight.bind(this,index)} className="checkTodo">
<i className="fa fa-check-circle" onClick={props.highlight} />
</div>
</li>
);
return <ul className="todos">{listItems}</ul>;
};
//controlled state component
class Layout extends React.Component {
constructor() {
super();
this.state = { text: "Hello", todo: [], highlight: false };
}
update(e) {
this.setState({ text: e.target.value });
}
handleClick() {
const text = this.state.text;
if (text.length > 0) {
this.setState(
{ todo: this.state.todo.concat(text), text: "", highlight: false },
function() {
console.log(this.state.todo);
}
);
} else {
alert("please enter something");
}
}
removeTodo(e) {
this.state.todo.splice(e, 1);
this.setState({ todo: this.state.todo });
}
changeHighlight(index, e) {
const highlight = this.state.highlight;
this.setState(prevState => ({
highlight: !prevState.highlight
}));
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-4 col-md-offset-4">
<div className="wrapper">
<h1>Todo List</h1>
<Input
value={this.state.text}
update={this.update.bind(this)}
handleClick={this.handleClick.bind(this)}
/>
<Displaytodo
removeTodo={this.removeTodo.bind(this)}
todo={this.state.todo}
changeHighlight={this.changeHighlight.bind(this)}
highlight={this.state.highlight}
/>
</div>
</div>
</div>
</div>
);
}
}
const app = document.getElementById("app");
ReactDOM.render(<Layout />, app);
https://codepen.io/mhal12/pen/MomWVg
Also when the user clicks the green tick, it will highlight the row by toggling class 'highlight' off and on, but in console it giving an error. which links to
https://facebook.github.io/react/docs/error-decoder.html?invariant=94&args[]=onClick&args[]=boolean
Simply remove the onClick on <i className="fa fa-check-circle" onClick={props.highlight} />.
As for the highlighting on each todo, it's a bit more complex. You have to have an id on each todo, and then pass the id to the changeHighlight function. You have to remove highlight from global state, and assign a highlight boolean on each todo. Then you have to display todos accordingly.
Same stuff for the removeTodo function, you pass in an id to remove it in the parent component.
Here's the full code :
const Input = props => {
return (
<div className="form-group">
<input
className="form-control"
value={props.value}
onChange={props.update}
type="text"
/>
<button className="btn btn-default" onClick={props.handleClick}>
Add Todo
</button>
</div>
);
};
const Displaytodo = (props) => {
const changeHighlight = function(id) {
props.changeHighlight(id);
}
const removeTodo = function(id) {
props.removeTodo(id);
}
const todolist = props.todo;
const listItems = todolist.map((todo, index) =>
<li
className={
todo.highlight ? 'list-unstyled todoItem highlight' : 'list-unstyled todoItem '
}
key={todo.id}>
{todo.text}
<div
onClick={removeTodo.bind(event, todo.id)}
className="removeTodo">
<i className="fa fa-trash" />
</div>
<div onClick={changeHighlight.bind(event, todo.id)} className="checkTodo">
<i className="fa fa-check-circle" />
</div>
</li>
);
return <ul className="todos">{listItems}</ul>;
};
class Layout extends React.Component {
constructor() {
super();
this.state = {text: "Hello", todo: []};
}
update(e) {
this.setState({ text: e.target.value });
}
handleClick() {
const text = this.state.text;
if (text.length > 0) {
this.setState(
{ todo: this.state.todo.concat({
id: this.state.todo.length + 1,
text: this.state.text,
highlight: false
}), text: ""},
function() {
console.log(this.state.todo);
}
);
} else {
alert("Please enter something");
}
}
removeTodo(id) {
let todos = this.state.todo;
for (let i = 0; i < todos.length; i++) {
let todo = todos[i];
if (todo.id == id) {
todos.splice(i, 1);
}
}
this.setState({ todo: todos });
}
changeHighlight(id) {
let todos = this.state.todo;
for (let i = 0; i < todos.length; i++) {
let todo = todos[i];
if (todo.id == id) {
todos[i].highlight = !todos[i].highlight;
}
}
this.setState({
todo : todos
});
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-4 col-md-offset-4">
<div className="wrapper">
<h1>Todo List</h1>
<Input
value={this.state.text}
update={this.update.bind(this)}
handleClick={this.handleClick.bind(this)}
/>
<Displaytodo
removeTodo={this.removeTodo.bind(this)}
todo={this.state.todo}
changeHighlight={this.changeHighlight.bind(this)}
/>
</div>
</div>
</div>
</div>
);
}
}
const app = document.getElementById("app");
ReactDOM.render(<Layout />, app);