I'm following Bob Ziroll's free scrimba course on React.
Thing is, my code is the same with his and it has been working so far...
but it isn't working anymore.
Here's my code
App.js
import React, { Component } from "react";
import TodoItem from "./components/TodoItem";
import todosData from "./components/todosData";
class App extends Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
console.log("PrevState Start ", prevState.todos )
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
console.log("Changed", id)
}
render() {
const todoItem = this.state.todos.map(x =>
<TodoItem handleChange = {this.handleChange}
key={x.id}
item={x}
/>
)
return (
<div>
{todoItem}
</div>
);
}
}
export default App;
And here's the code for TodoItem.js
import React from 'react'
function TodoItem(props) {
return (
<div>
<input type='checkbox' checked={props.item.completed} onChange={() => props.handleChange(props.item.id)} />
<p>{props.item.text}</p>
</div>
)
}
export default TodoItem
And here's todosData.js*
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true
},
{
id: 2,
text: "Grocery shopping",
completed: false
},
{
id: 3,
text: "Clean gecko tank",
completed: false
},
{
id: 4,
text: "Mow Lawn",
completed: true
},
{
id: 5,
text: "Catch up on arrested development",
completed: false
}
]
export default todosData
I've tried using a callback but it isn't working. I've checked prevState and the updated state, but no change is reflected.
I'd appreciate your help on this.
When you are updating the state using setState, You should return new object to tell react that this is the change and React will update it accordingly.
Live Demo
React will compare the reference of two objects and if you won't return new object then react will take as a same object. React won't figure out when you won't return new object. You are just updating a property of an object as :
if (todo.id === id) {
todo.completed = !todo.completed
}
You just have to make a small change as:
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
}
Being super curious, I tried your code and it works perfectly for me. Is this not what you wanted to happen? Check the snippet below:
document.onreadystatechange = () => {
const { useState, Component } = React;
class App extends Component {
constructor() {
super();
this.state = {
todos: todosData,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
this.setState((prevState) => {
console.log("PrevState Start ", prevState.todos);
const updatedTodos = prevState.todos.map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
return {
todos: updatedTodos,
};
});
console.log("Changed", id);
}
render() {
const todoItem = this.state.todos.map((x) => (
<TodoItem handleChange={this.handleChange} key={x.id} item={x} />
));
return <div>{todoItem}</div>;
}
}
function TodoItem(props) {
return (
<div>
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p>{props.item.text}</p>
</div>
);
}
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true,
},
{
id: 2,
text: "Grocery shopping",
completed: false,
},
{
id: 3,
text: "Clean gecko tank",
completed: false,
},
{
id: 4,
text: "Mow Lawn",
completed: true,
},
{
id: 5,
text: "Catch up on arrested development",
completed: false,
},
];
ReactDOM.render(<App />, document.querySelector("#root"));
};
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
Related
I started to learn React and I'm following a tutorial. I'm trying to change the checkboxes when it's clicked. I checked if handleChange function before writing the setState code and it's working. I did everything as in the tutorial but when I click to checkboxes, they are not changing. Looks like I'm missing something. Here is the code:
App.js :
import React, { Component } from "react";
import TodoItem from "./TodoItem";
import todosData from "./todosData";
class App extends Component {
constructor() {
super();
this.state = {
todos: todosData,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map((item) => (
<TodoItem key={item.id} item={item} handleChange={this.handleChange} />
));
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App;
TodoItem.js :
import React from "react";
import "./index.css";
function TodoItem(props) {
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p>{props.item.text}</p>
</div>
);
}
export default TodoItem;
todosData.js:
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true
},
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 3,
text: "Clean gecko tank",
completed: false
},
{
id: 4,
text: "Mow lawn",
completed: true
},
{
id: 5,
text: "Catch up on Arrested Development",
completed: false
}
]
export default todosData
Actually the problem is you're mutating the callback parameter in handleChange, it's generally highly recommended to avoid mutating them cause they lead to unexpected behavior. I'd rewrite like below:
class App extends Component {
constructor() {
super();
this.state = {
todos: todosData
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) return { ...todo, completed: !todo.completed };
return todo;
});
return {
todos: updatedTodos
};
});
}
render() {
const todoItems = this.state.todos.map(item => (
<TodoItem key={item.id} item={item} handleChange={this.handleChange} />
));
return <div className="todo-list">{todoItems}</div>;
}
}
You can check the result here: https://xd5w9.codesandbox.io/
I'm trying to make an event on todoList to check out the box, the event is called checkBox in my code, but I'm stuck and cannot figure it out. I'm very new to Javascript and React, could you please help me. I think there is something wrong here:
{this.state.itemList.map((item, index) => (
{item.todo}{item.completed}
checkBox={this.checkBox}
this.checkBox(item.completed)}
/>
))}
I cannot quite figure it out yet, here is my full code
import React, { Component } from 'react'
export default class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
todo:"",
completed: "",
itemList: [
{ todo: "Take out the Trash", completed: true },
{ todo: "Water the plants", completed: false },
{ todo: "Grocery shopping", completed: true }
]
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.checkBox = this.checkBox(this)
}
handleChange(e) {
this.setState({todo: e.target.value});
}
handleSubmit(n) {
this.setState({
itemList: [...this.state.itemList, {todo: this.state.todo, completed: false}],
});
}
checkBox(event) {
this.setState(prev =>{
const newList = prev.itemList.map(todo => {
if (todo.event === todo) {
todo.completed = !todo.completed
}
return todo
})
return {itemList: newList
}
})
}
render() {
return (
<div className="container">
<div className="main">
<div>
<input className="header w-50 p-2" type="text" placeholder="enter task" value={this.state.todo} onChange={this.handleChange}/><br></br>
<button className="button btn-btn-primary ml-1 mt-3" onClick={this.handleSubmit}>Submit</button>
</div>
<div>
{this.state.itemList.map((item, index) => (<p className="mt-4 list" key={index}>{item.todo}{item.completed} checkBox={this.checkBox} <input type="checkbox" onChange={()=>this.checkBox(item.completed)}/></p>))}
</div>
</div>
</div>
)
}
}
You did not bind the checkBox function properly in the constructor, I fixed it below and commented where the issue was:
constructor(props) {
super(props)
this.state = {
todo:"",
completed: "",
itemList: [
{ todo: "Take out the Trash", completed: true },
{ todo: "Water the plants", completed: false },
{ todo: "Grocery shopping", completed: true }
]
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.checkBox = this.checkBox.bind(this)// correctly bound function, instead of 'this.checkBox = this.checkBox(this)'
}
import React, { Component } from 'react';
import './App.css';
const list = [
{
title: 'React',
url: 'https://facebook.github.io/react/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://github.com/reactjs/redux',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
class App extends Component {
state = {
list,
text: 'abc',
searchTerm: ''
}
onDisMiss = (id) => {
const updateList = this.state.list.filter((item) => item.objectID != id)
return () => this.setState({ list: updateList })
}
onSearchChange = (event) => {
this.setState({ searchTerm: event.target.value })
}
isSearched = (searchTerm) => {
return (item) => item.title.toLowerCase().includes(searchTerm.toLowerCase())
}
render() {
const { searchTerm, list } = this.state
return (
<div>
<Search value={searchTerm}
onChange={this.onSearchChange}>Search</Search>
<Table list={list} pattern={searchTerm} onDissMiss={this.onDisMiss} />
</div>
);
}
}
class Search extends Component {
render() {
const { value, onChange, children } = this.props
return (
<div>
<form>
{children}<input type="text" onChange={onChange} value={value} />
</form>
</div>
);
}
}
class Table extends Component {
render() {
const { list, pattern, onDisMiss } = this.props
return (
<div>
{list.filter(isSearched(pattern)).map(item =>
<div key={item.objectID}>
<span><a href={item.url}>{item.title}</a></span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button onClick={onDisMiss(item.objectID)} type="button">Dismiss</button>
</span>
</div>)
}
</div>
);
}
}
export default App;
Road to react Book The Table component related.I get undefined for the isSearched method. how can I fix it so it works correctly its from the book road to react it seems like the book has a few error which I have problems solving because am just learning react. can you help with the solution and why this problem is actually happening
You should put the isSearched method inside the Table class and not the App class
// index.js
import React, { Component } from "react";
import MaterialTable, { MTableEditRow } from "material-table";
import axios from "axios";
import DataModel from "./DataModel";
import TitleInput from "./TitleInput";
class Report extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
workOrderOptions: [],
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
};
this.handleNewTitle = this.handleNewTitle.bind(this);
this.cancelAdd = this.cancelAdd.bind(this);
}
renderData() {
const URL = "https://jsonplaceholder.typicode.com/posts";
axios
.get(URL)
.then(response => {
this.setState({
data: response.data
});
})
.catch(error => {
console.log("ERROR:", error);
});
}
// I want to fire this method upon canceling the "add row"
cancelAdd() {
this.setState({
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
});
}
handleNewTitle(title) {
this.setState({
newEntry: {
// ...this.state.newEntry.title,
title: title
}
});
}
componentDidMount() {
this.renderData();
}
render() {
const columns = [
{
title: "ID",
field: "id",
editable: "never"
},
{
title: "User ID",
field: "userId",
editable: "never"
},
{
title: "Title",
field: "title",
editable: "never"
},
{
title: "Body",
field: "body",
editable: "never"
}
];
if (this.state.data) {
return (
<div>
<MaterialTable
components={{
EditRow: props => {
return (
<div>
<TitleInput
value={this.state.newEntry.title}
title={this.handleNewTitle}
/>
{/* <BodyInput
value={this.state.newEntry.body}
body={this.handleNewBody}
/>, <UserIDInput />, etc... */}
<MTableEditRow
{...props}
data={this.state.newEntry}
// Is there a handleCancelAction (or something ma something)?
</div>
);
}
}}
editable={{
// Just a sample add
onRowAdd: newData =>
new Promise((resolve, reject) => {
const result = {
id: 15465,
userId: 87946542,
title: this.state.newEntry.title,
body: "Old man Santiago"
};
console.log(result);
const data = this.state.data;
data.push(result);
this.setState({
...this.state
});
resolve();
})
}}
data={this.state.data}
columns={columns}
title={"Title"}
/>
</div>
);
} else if (!this.state.data) {
return <div>Loading...</div>;
}
}
}
export default Report;
// TitleInput.js
import React, { Component } from "react";
class TitleInput extends Component {
constructor(props) {
super(props);
this.handleTitleChanges = this.handleTitleChanges.bind(this);
}
handleTitleChanges(event) {
const title = event.target.value;
this.props.title(title);
}
render() {
return (
<div>
<select onChange={this.handleTitleChanges}>
<option selected hidden />
<option value="Old Man and the Sea">Old Man and the Sea</option>
<option value="Where the Red Fern Grows">
Where the Red Fern Grows
</option>
<option value="Nineteen Eighty-Four">Nineteen Eighty-Four</option>
<option value="The Kite Runner">The Kite Runner</option>
</select>
</div>
);
}
}
export default TitleInput;
// DataModel.js
export const DataModel = {
userId: "",
id: "",
title: "",
body: ""
};
You can see the sandbox example here: https://codesandbox.io/embed/festive-engelbart-7ned7
<MTableEditRow
{...props}
data={this.state.newEntry}
// on the onEditingCanceled prop, you can access the cancel method
// in this instance, we're clearing the state and then calling the
// method provided by the prop to close the showAddRow, we're passing
// mode, which will return "add"
onEditingCanceled={(mode, rowData) => {
this.cancelAdd();
props.onEditingCanceled(mode);
}}
/>
Line 309, (onEditingCanceled): https://github.com/mbrn/material-table/blob/master/src/material-table.js
// index.js
import React, { Component } from "react";
import MaterialTable, { MTableEditRow } from "material-table";
import axios from "axios";
import DataModel from "./DataModel";
import TitleInput from "./TitleInput";
class Report extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
workOrderOptions: [],
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
};
this.handleNewTitle = this.handleNewTitle.bind(this);
this.cancelAdd = this.cancelAdd.bind(this);
}
renderData() {
const URL = "https://jsonplaceholder.typicode.com/posts";
axios
.get(URL)
.then(response => {
this.setState({
data: response.data
});
})
.catch(error => {
console.log("ERROR:", error);
});
}
// I want to fire this method upon canceling the "add row"
cancelAdd() {
this.setState({
newEntry: {
userId: "",
id: "",
title: "",
body: ""
}
});
}
handleNewTitle(title) {
this.setState({
newEntry: {
// ...this.state.newEntry.title,
title: title
}
});
}
componentDidMount() {
this.renderData();
}
render() {
const columns = [
{
title: "ID",
field: "id",
editable: "never"
},
{
title: "User ID",
field: "userId",
editable: "never"
},
{
title: "Title",
field: "title",
editable: "never"
},
{
title: "Body",
field: "body",
editable: "never"
}
];
if (this.state.data) {
return (
<div>
<MaterialTable
components={{
EditRow: props => {
return (
<div>
<TitleInput
value={this.state.newEntry.title}
title={this.handleNewTitle}
/>
{/* <BodyInput
value={this.state.newEntry.body}
body={this.handleNewBody}
/>, <UserIDInput />, etc... */}
<MTableEditRow
{...props}
data={this.state.newEntry}
// looks like there is with onEditingCanceled
onEditingCanceled={(mode, rowData) => {
this.cancelAdd();
props.onEditingCanceled(mode);
}}
/>
</div>
);
}
}}
editable={{
// Just a sample add
onRowAdd: newData =>
new Promise((resolve, reject) => {
const result = {
id: 15465,
userId: 87946542,
title: this.state.newEntry.title,
body: "Old man Santiago"
};
console.log(result);
const data = this.state.data;
data.push(result);
this.setState({
...this.state
});
resolve();
})
}}
data={this.state.data}
columns={columns}
title={"Title"}
/>
</div>
);
} else if (!this.state.data) {
return <div>Loading...</div>;
}
}
}
export default Report;
I've been working on understanding React concepts and did my Todo project. I have the dummy data displaying, but can't add a new value to my dummy data, which is stored in an array of objects in a separate file, todos.js.
Here is the file hierarchy
Here is the error I am getting -
index.js:2177 Warning: Each child in an array or iterator should have a unique "key" prop.
TodoList.js
import React from 'react';
import Todo from './Todo';
import todos from '../todos'
class TodoList extends React.Component {
constructor() {
super();
this.state = {
todoItems: todos,
newItem: {}
}
}
addItem = (event) => {
event.preventDefault();
const todoList = this.state.todoItems;
todoList.push(this.state.newItem);
this.setState({
todoList: todos,
newItem: {}
});
};
handleInput = (event) => {
this.setState({ newItem: event.target.value });
}
render() {
const itenary = this.state.todoItems;
return (
<div>
{itenary.map(todo =>
<div key={todo.id}>
<Todo handleClick={this.props.handleClick} thing={todo} />
</div>
)}
<br />
<form onSubmit={this.addItem}>
<input type="text" onChange={this.handleInput} placeholder="Add a new task" />
<button>Submit</button>
</form>
</div>
);
}
}
export default TodoList;
Todo.js
import React from 'react';
class Todo extends React.Component {
constructor() {
super();
this.state = {
clicked: false
}
}
handleClick = () => {
this.setState({ clicked: !this.state.clicked });
}
render() {
const styles = this.state.clicked ? { textDecoration: 'line-through' } : { textDecoration: 'none' };
return (
{/* This is where the todo item is*/}
<div style={styles} onClick={this.handleClick} key={this.props.thing.id}>{this.props.thing.text}</div>
);
}
}
export default Todo;
todos.js
const todos = [
{ id: 1, text: 'Go to the gym', 'completed': false },
{ id: 2, text: 'Do laundry', 'completed': false },
{ id: 3, text: 'Study for exams', 'completed': false },
{ id: 4, text: 'Read a book', 'completed': false },
{ id: 5, text: 'Clean the bedroom', 'completed': false },
{ id: 6, text: 'Go to the park', 'completed': false },
];
export default todos;
Any help and/or feedback is appreciated.
You must give the new todo you add to todoItems a unique id that React can use to distinguish it from the others when you render them.
You should also not mutate the current state by using push. You should instead set state with an entirely new array that contains everything the previous one did.
Example
class TodoList extends React.Component {
constructor() {
super();
this.state = {
todoItems: todos,
newItem: ""
};
}
addItem = event => {
event.preventDefault();
this.setState(prevState => {
return {
todoItems: [
...prevState.todoItems,
{ id: Math.random(), text: prevState.newItem, completed: false }
],
newItem: ""
};
});
};
// ...
}
const todos = [
{ id: 1, text: "Go to the gym", completed: false },
{ id: 2, text: "Do laundry", completed: false },
{ id: 3, text: "Study for exams", completed: false },
{ id: 4, text: "Read a book", completed: false },
{ id: 5, text: "Clean the bedroom", completed: false },
{ id: 6, text: "Go to the park", completed: false }
];
class TodoList extends React.Component {
constructor() {
super();
this.state = {
todoItems: todos,
newItem: ""
};
}
addItem = event => {
event.preventDefault();
this.setState(prevState => {
return {
todoItems: [
...prevState.todoItems,
{ id: Math.random(), text: prevState.newItem, completed: false }
],
newItem: ""
};
});
};
handleInput = event => {
this.setState({ newItem: event.target.value });
};
render() {
const itenary = this.state.todoItems;
return (
<div>
{itenary.map(todo => (
<div key={todo.id}>
<Todo handleClick={this.props.handleClick} thing={todo} />
</div>
))}
<br />
<form onSubmit={this.addItem}>
<input
type="text"
onChange={this.handleInput}
value={this.state.newItem}
placeholder="Add a new task"
/>
<button>Submit</button>
</form>
</div>
);
}
}
class Todo extends React.Component {
constructor() {
super();
this.state = {
clicked: false
};
}
handleClick = () => {
this.setState({ clicked: !this.state.clicked });
};
render() {
const styles = this.state.clicked
? { textDecoration: "line-through" }
: { textDecoration: "none" };
return (
<div style={styles} onClick={this.handleClick} key={this.props.thing.id}>
{this.props.thing.text}
</div>
);
}
}
ReactDOM.render(<TodoList />, 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>
You can always define the Object/s array within a function accepting an array:
In this example:
const [programs, setPrograms] = useState([]);
In order to create a single JSON program array:
setPrograms([{id: program?.id, title: program?.title}]);
How to create Array in JSON.
[ { "id": "1", "text": "Hello", "status": "false" }, { "id": "2", "text": "Coding Techniques", "status": "true" }, ]
Watch this video for more help
https://youtu.be/zgFOIdBIn4w