react does not update DOM - javascript

import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./index.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
listItem: []
}
this.onChangeInput = this.onChangeInput.bind(this);
this.addToList = this.addToList.bind(this);
this.keyPress = this.keyPress.bind(this);
}
onChangeInput(event) {
this.setState({
text: event.target.value
});
}
addToList () {
let list = this.state.listItem;
list.push(this.state.text);
this.setState({
text: ""
});
this.setState({
listItem: list
});
}
deleteItem(event) {
console.log(event.target.remove());
}
keyPress (e) {
if (e.key === "Enter") {
this.addToList()
}
}
render() {
const listItem = this.state.listItem;
const list = listItem.map((val, i) =>
<li key={i.toString()} onClick={this.deleteItem}>
{val}
</li>
);
console.log(list);
return (
<div className="container">
<Input onChange={this.onChangeInput} value={this.state.text}
keyPress={this.keyPress}
/>
<Button addToList={this.addToList}/>
<ul>
{list}
</ul>
</div>
);
}
}
class Input extends Component {
render() {
return <input type="text" className="input" onChange={this.props.onChange}
onKeyPress={this.props.keyPress}
value={this.props.value}/>;
}
}
class Button extends Component {
render() {
return (
<button className="button" onClick={this.props.addToList}>
Add To List
</button>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I'm very confused and couldn't find solution any where.
I'm new to react.
when I delete the list items, I delete them from DOM but is in state and I didn't delete it from state.
I put console.log(list) in render method and on every key press logs list in console
my question is why DOM does not re-render lists and output those where deleted from DOM and not from state?
and why it works for new list items and ignore those that deleted from DOM ?

react dosent pickup the update in the way you are doing it
deleteItem(event) {
console.log(event.target.remove());
}
although the item will be removed , but react dosent have any clue that happend, to notify react that the items has changed and it need to re-render, you need to call setState , then react calls the render method again,
deleteItem(e) {
const list= this.state.listItem;
list.pop() // remove the last element
this.setState({
list: list
});
}

Related

"Cannot read property 'map' of undefined" within React, what's wrong here?

Trying to get my head around props so forgive me if its a silly mistake. I am trying to pass all of my data into one variable and pass that out into props (using {item.text} and {item.key}), however, my ".map" isn't picking up anything and there's a bunch of errors, what's wrong with my code?
The problem lays specifically here in this block of code
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
Here is the code in full
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList(input) {
let listArray = this.state.list;
listArray.push(input);
var newItem = {
text: listArray,
key: Date.now()
};
this.setState(prevState => {
return {
list: prevState.list.concat(newItem)
};
});
this.setState({
list: listArray
})
}
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList(this.state.userInput)}>Press me</button>
<ul>
{this.testingSetup()}
</ul>
</div>
);
}
}
export default TodoListt;
You can use the spread operator to add to an existing array. Simply add a new object to the array in the state, and then clear the user input, ready for another item. Based on your code, here's a simple example of adding to a state list (haven't run myself, so just check for syntax errors and such):
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoList extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
const { list, userInput } = this.state;
// Add item to state list using spread operator and clear input
this.setState({
list: [...list, {text:userInput, key: Date.now()}],
userInput: ""
});
}
render() {
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList()}>Press me</button>
<hr/>
{/* For each item in the list, render the contents */}
{this.state.list.map(item => (
<div key={item.key}>
<h3>{item.text}</h3>
<p>Time: {item.key}</p>
</div>
))}
</div>
);
}
}
export default TodoList;

Trying to get my delete button to work on a React Component

I'm working on my First project with React, I have an App and a ToDo. I am defining a deleteToDo method and I want the method to call this.setState() and pass it a new array that doesn't have the to-do item being deleted with the use of the .filter() array method. I don't want to alter the code to much or introduce more complexity. In essence I would like to keep it as straight forward as possible. I am still a beginner with React so this has been a big learning process. I feel that I am close.
This is the main app
import React, { Component } from 'react';
import './App.css';
import ToDo from './components/ToDo.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ description: 'Walk the cat', isCompleted: true },
{ description: 'Throw the dishes away', isCompleted: false },
{ description: 'Buy new dishes', isCompleted: false }
],
newTodoDescription: ''
};
}
deleteToDo(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.deleteToDo = this.state.filter(index);
this.setState({ todos: todos });
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { description: this.state.newTodoDescription, isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo], newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
render() {
return (
<div className="App">
<ul>
{ this.state.todos.map( (todo, index) =>
<ToDo key={ index } description={ todo.description } isCompleted={ todo.isCompleted } toggleComplete={ this.toggleComplete } deleteToDo={ this.deleteToDo } />
)}
</ul>
<form onSubmit={ (e) => this.handleSubmit(e) }>
<input type="text" value={ this.state.newTodoDescription } onChange={ (e) => this.handleChange(e) } />
<input type="submit" />
</form>
</div>
);
}
}
export default App;
And this the ToDo aspect
import React, { Component } from 'react';
class ToDo extends Component {
render() {
return (
<li>
<button type="button" onClick={ this.props.deleteTodo} > delete </button>
<input type="checkbox" checked={ this.props.isCompleted } onChange={ this.props.toggleComplete } />
<span>{ this.props.description }</span>
</li>
);
}
}
export default ToDo;
You slice and array without the index, that's may be why your delete not work
deleteToDo(index) {
const todos = this.state.todos.slice(index, 1);
this.setState({ todos: todos });
}
1) You need to bind your deleteToDo method in the constructor
this.deleteToDo = this.deleteToDo.bind(this);
2) You need to set a new property on the component that is the same as its index.
<ToDo
key={index}
id={index}
description={ todo.description }
// ...
/>
3) Then you can pass that index as the argument to deleteToDo (making sure you spell the method name correctly).
<button
type="button"
onClick={() => this.props.deleteToDo(this.props.index)}
>Delete
</button>
4) Finally, you can strip down your deleteToDo method to the following:
deleteToDo(index) {
// Return a new array that doesn't
// have a row with a matching index
const todos = this.state.todos.filter((el, i) => i !== index);
this.setState({ todos });
}
Here's a working version.

setState() doesn't update value

I'm trying to update state of my component and it's not working. I have Main page container component => nested Navbar container component => nested NavItem UI component. Also on Main page I have AddNavItem container component which should add item to Navbar. It looks like this:
Main
|
|--NavBar
| |
| |--NavItem
|
|--AddNavItem
This is my Main page code:
import React, { Component } from 'react';
import Navbar from '../nav/Navbar'
import AddNavItem from '../fields/AddNavItem'
class Main extends Component {
state = {
items: [
{id: 1, name: 'Услуги', link: 'services'},
{id: 2, name: 'Цены', link: 'prices'},
{id: 3, name: 'Как это работает?', link: 'works'},
]
}
addNavItem = (item) => {
this.setState((state) => {
let newItems = [...state.items];
newItems.unshift(item)
console.log(newItems);
return {
items: newItems
}
});
}
render() {
return (
<div>
<Navbar items={ this.state.items }/>
<AddNavItem addNavItem={ this.addNavItem }/>
</div>
);
}
}
export default Main;
The problem is that I always get old array with 3 initial obj even after addNavItem is firing. <Navbar /> still gets array with 3 element. I have read about async setState and makes as describe in doc https://reactjs.org/docs/faq-state.html What have I doing wrong?
UPD: I changed code (unshift) to return array, not a length of the array. Console log shows me that I get new array with 4 obj
UPD: My complete code for all components:
Navbar
import React, { Component } from 'react';
import NavItem from './NavItem';
class Navbar extends Component {
state = {
items: this.props.items,
}
render() {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ this.state.items }/>
</ul>
</div>
);
};
}
export default Navbar;
NavItem:
import React from 'react';
const NavItem = ({ items }) => {
const menuItemList = items.map((item) => {
return (
<li key={item.id}>
{ item.name }
</li>
);
})
return (
<div>
{ menuItemList }
</div>
)
}
export default NavItem;
AddNavItem:
import React, { Component } from 'react';
class AddNavItem extends Component {
state = {
id: Math.random(),
name: null,
link: null
}
handleInput = (e) => {
this.setState({
[e.target.id]: e.target.value,
});
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addNavItem(this.state);
}
render() {
return (
<div className="centered flex-column-centered">
<form onSubmit={ this.handleSubmit }>
<h4 className="labelField">Название раздела:</h4>
<input
className="inputField"
type="text"
id="name"
placeholder="укажите название"
onChange={ this.handleInput } />
<h4 className="labelField">URL:</h4>
<input
className="inputField"
type="text"
id="link"
placeholder="укажите ссылку"
onChange={ this.handleInput } />
<button className="submitBtn">Добавить</button>
</form>
</div>
);
}
}
export default AddNavItem;
According to Mozilla docs
The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.
So when you try to change your state using this, it's returning the length of the array instead of the new array you're trying to update the state to. On top of that, this method is mutable as you are modifying the original array instead of returning a new copy of it. I would suggest changing your code to use the array.prototype.concat() method instead as it keeps your state immutable as it returns a new array instead of modifying the original array.
Try changing your method to this instead.
addNavItem = (item) => {
this.setState({ items: this.state.items.concat(item) });
}
Edit:
If you are using es6, you can use the spread syntax which also returns a new copy of the array.
addNavItem = (item) => {
this.setState({ items: [...this.state.items, item] });
}
Edit 2:
In your Navbar component, you're setting this.props.items to state when it initializes and then you don't update your state when this.props.items change.
You don't need state for this component, I would change your NavBar component to look like this:
import React from 'react';
import NavItem from './NavItem';
const Navbar = (props) => {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ props.items }/>
</ul>
</div>
);
}
export default Navbar;
Edit 3:
If you want to keep your items inside state in your NavBar component, then you need to watch for updates being made to the items props on that component.
import React, { Component } from 'react';
import NavItem from './NavItem';
class Navbar extends Component {
state = {
items: this.props.items,
}
componentDidUpdate(prevProps) {
if (this.props.items!== prevProps.items) {
this.setState({ items: this.props.items });
}
}
render() {
return (
<div className="centered navbar grey">
<h2>MyReact</h2>
<ul>
<NavItem items={ this.state.items }/>
</ul>
</div>
);
};
}
export default Navbar;
let arr = [3,4,5]
console.log(arr.unshift(3,5))
addNavItem = (item) => {
let newItems = [...this.state.items];
newItems.unshift(item)
this.setState({
items: newItems
}
});
}
Now you are updating the array and adding the elements to the begining of it, which i assume you wanted and is why you picked upshift. you are then setting the sate equal to the updated array you have.
addNavItem = (item) => {
this.setState((prevState) => {items: [...prevState.items, item]})
}
This should work.

Updating a list in React.js based on whether a new element was added or existing was edited

I have App as my main component and TodoList to render my list of todos. When Add Task button is clicked, it opens an input which autosaves the input that we type. This is saved in a state addTaskInput which is then passed to TodoList component as a prop addItem when the focus on input is removed.
Now, we may edit the same element by simply clicking on it and typing. In that case, I want to update the same component. But if a new item is added, I want to add a new item to list.
1) How to check this?
2) I want to update my todo state inside TodoList component when list updates. Where should I call setState for that?
class App extends Component {
state = {
showInput: false,
addTaskInput: '',
addItem:''
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
populateTaskList = (e) => {
this.setState({addItem: e.target.value})
}
render() {
const {showInput, addTaskInput, addItem} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
onBlur={this.populateTaskList}
/> }
<TodoList
addItem={addItem}
/>
</div>
);
}
}
class TodoList extends Component {
state = {
todoList: ['a','aaa'],
todo: []
}
componentDidMount(){
const {todo, todoList} = this.state;
todoList.map((val) => {
this.state.todo.push(<div key={val}>{val}</div>)
})
this.setState({todo});
}
static getDerivedStateFromProps(nextProps, prevState){
return{
...prevState,
...nextProps
}
}
render () {
const {addItem, todoList, todo} = this.state;
return(
<div>
{todo}
</div>
)
}
}
You can keep the todoList in your app Component and pass that variable to the TodoList component:
class TodoList extends Component {
static getDerivedStateFromProps(nextProps, prevState){
return{
...prevState,
...nextProps
}
}
render () {
return(
<div>
{this.props.todoList.map((val) => <div key={val}>{val}</div>)}
</div>
)
}
}
And your app Component
class App extends Component {
state = {
showInput: false,
addTaskInput: '',
addItem:'',
todoList: [],
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
populateTaskList = (e) => {
const actualList = this.state.todoList;
actualList.push(e.target.value);
this.setState({addItem: e.target.value, todoList:actualList })
}
render() {
const {showInput, addTaskInput, addItem, todoList} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
onBlur={this.populateTaskList}
/> }
<TodoList
todoList={todoList}
/>
</div>
);
}
}
1) The only way I see is you maintain a key or index for each item only then you could update existing item. There is no way for you to know if the item is new or existing based on its value
2) To be able to update Todo's state you should give "addItem(item)" function on Todo which can be called with value that needs to be added/updated. And in that function you could update Todo's state. This function can be called from TodoList when text box loses focus or user stops typing.

How to add to state array in React

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state

Categories

Resources