I am aware of similar threads here, but any of them still can't help me.
I'm trying to pass deleteItem() function from parent component to onClick argument in grandson component.
Please, look at components and tell me what is wrong, what should I change to access this function in grandson component?
Parent - https://codeshare.io/2E39oO
Child - https://codeshare.io/5XnwN8
Grandson - https://codeshare.io/5z9JXE
Here are the two things I spotted
misspelling in deleteHandler (already mentioned)
the button was disabled, so it wouldn't trigger an event
Example I ended up with
class ToDo extends Component {
constructor(props) {
super(props);
this.state = {
list: [
{
title: "Cup cleaning",
todo: "Wash and take away the Kurzhiy's cup from WC"
},
{
title: "Smoking rollton",
todo: "Do some rollton and cigarettes"
},
{
title: "Curious dream",
todo: "Build a time machine"
}
],
title: "",
todo: ""
};
}
createNewToDoItem = () => {
this.setState(({ list, title, todo }) => ({
list: [
...list,
{
title,
todo
}
],
title: "",
todo: ""
}));
};
handleKeyPress = e => {
if (e.target.value !== "") {
if (e.key === "Enter") {
this.createNewToDoItem();
}
}
};
handleTitleInput = e => {
this.setState({
title: e.target.value
});
};
handleTodoInput = e => {
this.setState({
todo: e.target.value
});
};
deleteItem(indexToDelete) {
console.log("HERE");
this.setState(({ list }) => ({
list: list.filter((toDo, index) => index !== indexToDelete)
}));
}
editItem = (i, updTitle, updToDo) => {
let arr = this.state.list;
arr[i].title = updTitle;
arr[i].todo = updToDo;
this.setState({ list: arr });
};
eachToDo = (item, i) => {
return (
<ToDoItem
key={i}
title={item.title}
todo={item.todo}
deleteItem={this.deleteItem.bind(this, i)}
editItem={this.editItem.bind(this, i)}
/>
);
};
render() {
return (
<div className="ToDo">
<h1 className="ToDo-Header" />
<div className="ToDo-Container">
<div className="ToDo-Content">
{this.state.list.map(this.eachToDo)}
</div>
<div>
<input
type="text"
placeholder="Enter new title"
value={this.state.title}
onChange={this.handleTitleInput}
onKeyPress={this.handleKeyPress}
/>
<input
type="text"
placeholder="Enter new todo"
value={this.state.todo}
onChange={this.handleTodoInput}
onKeyPress={this.handleKeyPress}
/>
{/* <AddButton addHandler={this.createNewToDoItem} /> */}
</div>
</div>
</div>
);
}
}
class ToDoItem extends Component {
constructor(props) {
super(props);
this.state = {
editMode: false
};
}
edit = () => {
this.setState({ editMode: true });
};
save = () => {
let updTitle = this.refs.newTitle.value;
let updToDo = this.refs.newToDo.value;
this.props.editItem(updTitle, updToDo);
this.setState({
editMode: false
});
};
renderNormal = () => {
return (
<div className="ToDoItem">
<p className="ToDoItem-Text">{this.props.title}</p>
<p className="ToDoItem-Text">{this.props.todo}</p>
{/* <EditButton editHandler={this.edit} /> */}
<FloatingActionButtons deleteHandler={this.props.deleteItem} />
{/* <button className="ToDoItem-Button" id="editbtn" onClick={this.edit}>✍</button> */}
{/* <button className="ToDoItem-Button" id="delbtn" onClick={this.props.deleteItem}>−</button> */}
</div>
);
};
renderEdit = () => {
return (
<div className="ToDoItem">
<textarea ref="newTitle" defaultValue={this.props.title} />
<textarea ref="newToDo" defaultValue={this.props.todo} />
<button onClick={this.save} className="ToDoItem-Button" id="savebtn">
💾
</button>
</div>
);
};
render() {
if (this.state.editMode) {
return this.renderEdit();
} else {
return this.renderNormal();
}
}
}
const styles = theme => ({
button: {
margin: theme.spacing.unit
}
});
function FloatingActionButtons(props) {
return (
<div>
<Button variant="fab" aria-label="Delete" onClick={props.deleteHandler}>
Delete
</Button>
</div>
);
}
FloatingActionButtons.propTypes = {
classes: PropTypes.object.isRequired
};
Related
So I have stated learning react and tried to make a project that renders data from an api. I have 2 components, a Search bar and a component that renders the weather.
What I'm trying to do is to get the value from the search bar and concatenate into the api string. I have tried doing this by settings a prop but I am unable accessing it in the weather component.
My questions is: How can I access the search value in a different component
/components/Search.js
class Search extends Component {
state = {
title: '',
};
onChange = (e) => {
this.setState({ title: e.target.value });
};
onSubmit = (e) => {
// e.preventDefault();
this.props.searchValue(this.state.title);
this.setState({ title: '' });
};
render() {
return (
<Mui.Container>
<CssBaseline />
<form
onSubmit={this.onSubmit}
autoComplete='off'
>
<Mui.Input
placeholder='enter place'
value={this.state.title}
onChange={this.onChange}
/>
</form>
</Mui.Container>
);
}
}
Search.propTypes = {
searchValue: PropTypes.func,
};
/components/Weather.js
class Weather extends Component {
state = {
videos: [],
};
componentDidMount = () => {
axios
.get(
'<weather api here>'
)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
I assume there will be a parent component for <Weather /> and <Search />?
If yes then the parent component can have state, and you pass your setState function into the search component, and then you pass the current state into the weather component.
<Weather searchValue="current state from parent" />
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
videos: []
};
}
componentDidMount = () => {
axios
.get(`URL?${this.props.searchValue}`)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
im trying to make the title of the movie clicked to be the in search input value.
exapmle: searched for "batman", clicked on result "batman ninja", search input becomes "batman ninja"
Search Component:
handleSelect is the function im passing to each search result
class MoviesSearch extends Component {
state = {
display: false,
title: ''
};
handleChange = e => {
this.props.movieSearch(e.target.value);
this.setState({
display: false
});
};
handleSubmit = e => {
e.preventDefault();
const { searchInput } = this.props;
this.props.fetchMovie(searchInput);
this.setState({
display: true
});
};
handleSelect = e => {
// this.setState({title: e.target.value})
// this.setState({title: e.target.value})
// this.setState({display: false, title: this.props.movies.Title})
// this.props.movieSearch(this.state.title)
}
render() {
//////////////////////////////////////////
////STATE:
const { movies } = this.props;
//////////////////////////////////////////
//// BUTTONS:
const btnDisabled = (
<button type="submit" disabled>
Search
</button>
);
const btnEnabled = <button type="submit">Search</button>;
///////////////////////////////////////////
//// DISPLAY CONTENT:
const display = (
<div className="dropdown-content">
<MovieList select={this.handleSelect}/>
</div>
);
///////////////////////////////////////////
console.log(this.state.title)
return (
<div className="movieSearch">
<form className="searchForm" onSubmit={this.handleSubmit}>
<div
className={movies.length === 0 ? "dropdown" : "dropdown is-active"}
>
<input
type="text"
placeholder="Enter Movie Name"
onChange={this.handleChange}
value={this.props.movies.Title}
/>
<div className="dropdown-menu">
{this.state.display ? display : null}
</div>
{this.props.searchInput.length <= 0 ? btnDisabled : btnEnabled}
</div>
</form>
</div>
);
}
}
const mapStateToProps = state => ({
searchInput: state.movies.searchInput,
movies: state.movies.movies
});
export default connect(mapStateToProps, { movieSearch, fetchMovie })(
MoviesSearch
);
MovieItem Component:
selectedItem is the function that was passed (handleSelect)
const MovieItem = ({ movie, selectedItem }) => {
return (
<NavLink className="dropdown-item" to="#" onClick={(e)=>selectedItem(e.movie.Title)}>
<img
className="poster"
src={movie.Poster === "N/A" ? "" : movie.Poster}
alt=""
/>
<span className="title">{movie.Title}</span>
</NavLink>
);
};
export default MovieItem;
In MovieItem component, use onClick={(e)=>selectedItem(movie.Title)}
In your parent component, use
handleSelect = value => {
this.setState({title: value})
}
I have a select dropdown that when selected renders a checkbox group using <FieldArray> from formik
<FieldArray
name="fields"
render={arrayHelpers => (
<div>
{fields.map(field => (
<div key={field.name}>
<label>
<input
name="fields"
type="checkbox"
value={field.name}
onChange={e => {
if (e.target.checked) arrayHelpers.push(field.name);
else {
const idx = fields.indexOf(field.name);
arrayHelpers.remove(idx);
}
}}
/>{" "}
{field.name}
</label>
</div>
))}
</div>
)}
/>
So in the onChange method I need for each checkbox that is selected to render a class component that has additional input fields that are tied to the field name. For example, the size and length options need to chosen for each checkbox that is selected.
class FieldInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
lengthType: "",
size: [],
};
this.lengthTypeChange = this.lengthTypeChange.bind(this);
this.onSizeChange = this.onSizeChange.bind(this);
}
lengthTypeChange = lengthType => {
//handle change method for lengthType
this.setState({ lengthType });
console.log("LengthType selected: ", lengthType);
};
onSizeChange = e => {
this.setState({ [e.target.name]: e.target.value });
console.log([e.target.value]);
};
render() {
return (
<div>
<h2>
{" "}
These are the input fields for each field name checkbox selected.{" "}
</h2>
<div>
<Select
id="color"
options={lengthTypeOptions}
isMulti={false}
value={lengthType}
onChange={this.lengthTypeChange}
onBlur={this.handleBlur}
placeholder={"Select a lengthType..."}
/>
</div>
<div>
<label>Size:</label>
<input
value={this.state.size}
onChange={this.onSizeChange}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</div>
</div>
);
}
}
For instance each field.name is rendered to a checkbox and should have a select dropdown for lengthType and input for size like so:
{
field.name: {
size: 1,
lengthType: "Fixed"
}
}
So I have the component designed, just need to render it to each checkbox that is selected.
How can I pass the FieldInput component to the checkboxes based on if they are toggled or not?
Inside the <Myselect> component, you can find the <FieldArray> component, using the onChange method
onChange={e => {
if (e.target.checked) {
arrayHelpers.push(field.name);
When's it's checked it also needs to render <FieldInputs>
Here's a link to a sandbox with the classes/components described above
I made many changes this is the full code
import "./helper.css";
import { MoreResources, DisplayFormikState } from "./helper";
import React from "react";
import { render } from "react-dom";
import { Formik, FieldArray } from "formik";
import * as Yup from "yup";
import axios from "axios";
import Select from "react-select";
var MockAdapter = require("axios-mock-adapter");
var mock = new MockAdapter(axios);
mock.onGet("/dataschemas").reply(200, {
data: [
{
id: "2147483602",
selfUri: "/dataschemas/2147483602",
name: "Phone Data"
}
]
});
mock.onGet("/dataschemas/2147483602").reply(200, {
data: {
id: "2147483602",
selfUri: "/dataschemas/2147483602",
type: "DataSchema",
name: "Phone Record",
fields: [
{
name: "action"
},
{
name: "callee"
},
{
name: "caller"
},
{
name: "duration"
},
{
name: "message"
},
{
name: "time_stamp"
}
]
}
});
const lengthTypeOptions = [
{ value: "fixed", label: "Fixed" },
{ value: "variable", label: "Variable" }
];
class FieldInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
lengthType: "",
size: []
};
this.lengthTypeChange = this.lengthTypeChange.bind(this);
this.onSizeChange = this.onSizeChange.bind(this);
}
lengthTypeChange = lengthType => {
//handle change method for lengthType
this.setState({ lengthType }, () => {
this.props.update(this.props.name, this.state);
});
//console.log("LengthType selected: ", lengthType);
};
onSizeChange = e => {
this.setState({ [e.target.name]: e.target.value }, () => {
this.props.update(this.props.name, this.state);
});
//console.log([e.target.value]);
};
render() {
const { lengthType } = this.state;
return (
<div>
<h2>
{" "}
These are the input fields for each field name checkbox selected.{" "}
</h2>
<div>
<Select
id="color"
options={lengthTypeOptions}
isMulti={false}
value={lengthType}
onChange={this.lengthTypeChange}
onBlur={this.handleBlur}
placeholder={"Select a lengthType..."}
/>
</div>
<div>
<label>Size:</label>
<input
value={this.state.size}
onChange={this.onSizeChange}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</div>
</div>
);
}
}
const App = () => (
<div className="app">
<h1>Formik Demo</h1>
<Formik
initialValues={{
querySchemaName: "",
schemas: [],
fields: [],
selectorField: "",
lengthType: "",
size: []
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
validationSchema={Yup.object().shape({
querySchemaName: Yup.string()
.required("QuerySchema name is required!")
.min(3, "Please enter a longer name")
.max(50, "Please ener a shorter name")
})}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
setFieldValue,
setTouchedValue
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="querySchemaName" style={{ display: "block" }}>
QuerySchema Name:
</label>
<input
id="querySchemaName"
placeholder="Example -- QuerySchema1"
type="text"
value={values.querySchemaName}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.querySchemaName && touched.querySchemaName
? "text-input error"
: "text-input"
}
/>
{errors.querySchemaName && touched.emaquerySchemaNameil && (
<div className="input-feedback">{errors.querySchemaName}</div>
)}
<MySelect
value={values.schemas}
onChange={setFieldValue}
options={values.schemas}
onBlur={setTouchedValue}
error={errors.topics}
touched={touched.topics}
/>
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
<MoreResources />
</div>
);
class MySelect extends React.Component {
constructor(props) {
super(props);
this.state = {
schemas: [],
fields: [],
selectorField: "",
checked: []
};
this.handleChange = this.handleChange.bind(this);
this.updateSelectorField = this.updateSelectorField.bind(this);
}
componentDidMount() {
axios.get("/dataschemas").then(response => {
this.setState({
schemas: response.data.data
});
//console.log(this.state.schemas);
});
}
handleChange = value => {
// this is going to call setFieldValue and manually update values.dataSchemas
this.props.onChange("schemas", value);
const schema = this.state.schemas.find(
schema => schema.name === value.name
);
if (schema) {
axios.get("/dataschemas/2147483602").then(response => {
this.setState({
fields: response.data.data.fields
});
//console.log("fields are: " + this.state.fields);
});
}
};
updateSelectorField = value => {
this.props.onChange("selectorField", value);
};
update = (name, value) => {
this.setState(prev => {
var arr = prev.checked;
var de = null;
for (var j = 0; j < arr.length; j++) {
if (arr[j].name === name) {
de = j;
}
}
arr[de] = Object.assign(arr[de], value);
console.log(arr);
return { checked: arr };
});
};
handleBlur = () => {
// this is going to call setFieldTouched and manually update touched.dataSchemas
this.props.onBlur("schemas", true);
};
checker = field => {
var d = -1;
for (let j = 0; j < this.state.checked.length; j++) {
if (this.state.checked[j].name === field.name) {
d = j;
}
}
if (d >= 0) {
return (
<FieldInputs
key={field.name + 1}
name={field.name}
update={this.update}
/>
);
} else {
return null;
}
};
change = e =>{
var arr = this.state.checked;
var de = -1;
for (var j = 0; j < arr.length; j++) {
if (arr[j].name === e) {
de = j;
}
}
if(de >= 0){
delete arr[de];
}else{
var arr = arr.concat([
{
name: e,
size: 1,
lengthType: "Fixed"
}
])
}
var nar = [];
for(let i=0; i<arr.length; i++){
if(typeof arr[i] !== "undefined"){
nar.push(arr[i])
}
}
this.setState({checked: nar});
}
render() {
const schemas = this.state.schemas;
const fields = this.state.fields;
return (
<div style={{ margin: "1rem 0" }}>
<label htmlFor="color">
DataSchemas -- triggers the handle change api call - (select 1){" "}
</label>
<Select
id="color"
options={schemas}
isMulti={false}
value={schemas.find(({ name }) => name === this.state.name)}
getOptionLabel={({ name }) => name}
onChange={this.handleChange}
onBlur={this.handleBlur}
placeholder={"Pick a DataSchema..."}
/>
<label htmlFor="color">Selector Field - (select 1) </label>
<Select
id="color"
options={fields}
isMulti={false}
value={fields.find(({ name }) => name === this.state.name)}
getOptionLabel={({ name }) => name}
onChange={this.updateSelectorField}
placeholder={"Select a Selector Field..."}
/>
{!!this.props.error && this.props.touched && (
<div style={{ color: "red", marginTop: ".5rem" }}>
{this.props.error}
</div>
)}
<div>
<FieldArray
name="fields"
render={arrayHelpers => (
<div>
{fields.map(field => (
<React.Fragment>
<div key={field.name}>
<label>
<input
name="fields"
type="checkbox"
value={field.name}
onChange={(e) => {
this.change(field.name)
if (e.target.checked) {
arrayHelpers.push(field.name);
} else {
const idx = fields.indexOf(field.name);
arrayHelpers.remove(idx);
}
}}
/>{" "}
{field.name}
</label>
</div>
{this.checker(field)}
</React.Fragment>
))}
</div>
)}
/>
</div>
</div>
);
}
}
render(<App />, document.getElementById("root"));
here is the codesandbox
If i click on a particular ToDos edit button, its value should be defaulted inside the textarea but everytime the last ToDo is defaulting, can somebody please help, whether using ref is a right choice or something else, then where i m wrong what i'm suppose to do ?
handleEdit() {
e.preventDefault();
.....
}
renderDisplay() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
<div>
<button className="btn btn-primary btn-xs glyphicon glyphicon-pencil"
onClick={this.handleEdit.bind(this)}
/>
</div>
<hr/>
</li>
</div>
)})
}
</div>
);
}
renderForm() {
return(
<div>
<textarea className="form-control" defaultValue={this.refs.newText.innerText} rows="1" cols="100" style={{width: 500}}/>
</div>
)
}
render() {
if(this.state.editing) {
return this.renderForm();
}else {
return this.renderDisplay();
}
}
}
First of all you are using an old ref API. You should use this one, where you set the ref to the instance of the class using this with a callback.
<input ref={ref => {this.myInput = ref}} />
And then you can access its value by just referring to this.myInput .
As for your "bug", keep in mind that you are looping over and overriding the ref. so the last ref assignment would be the last item in the array.
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
There will always be 1 newText ref and it will always be the last item in the array.
You should render different ref names according to the item id and then pass the id of the item to the renderForm so it can access the relevant ref.
With that said, i really recommend to extract the todo to a different component as well as the form. I don't see a valid reason to use refs in this case.
Edit
As a follow-up to your comment, here is a small example of how you would use components instead of refs in order to get information from the child like values etc..
class Todo extends React.Component {
onClick = () => {
const { todoId, onClick } = this.props;
onClick(todoId);
}
render() {
const { value, complete } = this.props;
return (
<div
style={{ textDecoration: complete && 'line-through' }}
onClick={this.onClick}
>
{value}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: '1', value: 'to do this', complete: false },
{ id: '2', value: 'todo that', complete: true },
{ id: '3', value: 'will do it later', complete: false }]
}
}
toggleTodo = (todoId) => {
const { todos } = this.state;
const nextState = todos.map(todo => {
if (todo.id !== todoId) return todo;
return {
...todo,
complete: !todo.complete
}
});
this.setState({ todos: nextState });
}
render() {
const { todos } = this.state;
return (
<div >
{
todos.map((todo) => {
return (
<Todo
complete={todo.complete}
key={todo.id}
todoId={todo.id}
value={todo.value}
onClick={this.toggleTodo}
/>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
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);