I'm creating a todo list with React and found out that we use states in react unlike innerHTML or appendChild() in Javascript.
Here's where I'm facing the problem:
When a user clicks a Button, a simple todo is added to the parent Div, and I mean 'Added not Replaced.
However, when using react hooks useState(), It's just replacing the element, but I want to Add it to the div. Here's the code:
export default function TodoContainer() {
let [item, setItem] = useState('Nothing to show...');
function handleClick() {
setItem(item += <TodoItems/>)
}
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr/>
{item}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
}
So, when using setItem(item + <TodoItem/>), it just shows : [object Object]
Please help me, I don't know almost nothing about react as I just recently started learning it.
By the way, the <TodoItems/> returns a div with a todo and its detail inside of it.
Thanks.
Yes, when you item += <TodoItems/> you are appending a JSX literal to a (possibly) string, and the result seems to be interpreted as a Javascript object. Objects are not valid JSX and renderable.
You may want an array of todos for the state, and when adding a new todo add only the data, not the JSX. When adding a todo to the array you need to create a new array reference and shallow copy the previous state. You'll map the state to JSX in the render return.
Example:
export default function TodoContainer() {
const [todos, setTodos] = useState([]);
function handleClick() {
setTodos(todos => [...todos, "new todo"]);
}
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr/>
{todos.length
? todos.map(todo => <TodoItems key={todo} todo={todo} />)
: 'Nothing to show...'
}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
}
Related
i created a parent component that gathers a bunch of child components:
Expenses - Parent,
ExpenseItem - child.
i defined the first value of ExpenseItem by hard coding inside of Expenses
i entered the dynamic value to the ExpenseItem component element and then used "props" parameter to get the data from ExpenseItem to Expenses.
function Expenses() {
const Data = [{
title: `מסרק`,
date: Date(),
amount: 2.95,
},
]
return (
<div>
<ExpenseItem
title={Data[0].title}
date={Data[0].date}
amount={Data[0].amount}
/>
</div>
);
}
now, i want to update the values through a button "edit" in ExpenseItem Component.
when i do update values through useState and console log them i see the updated values,
but, the component doesnt re-renders so i see the prev value on the screen. though if i try to hard code the value it does re-renders and changes the value on the screen.
function ExpenseItem(props) {
const [title, setTitle] = useState(props.title);
const [date, setDate] = useState(props.date);
const [amount, setAmount] = useState(props.amount);
const clickHandle = () => {
console.log(title);
setTitle("חתול")
console.log(Date());
setDate(date)
console.log(amount);
setAmount("222")
}
return (
<div className="ExpenseItem">
<div className="ExpenseItem_EditFunctions">
<p className="ExpenseItemDate">{props.date}</p>
<div className="ExpenseItem_EditFunctions_Icons">
<span className="material-icons delete">delete</span>
<span className="material-icons edit" onClick={clickHandle}>
edit
</span>
</div>
<div className="ExpenseItem_MainContent">
<h3 className="ExpenseItemTitle">{props.title}</h3>
<p className="ExpenseItemAmount">₪{props.amount}</p>
</div>
</div>
</div>
);
}
It's because you are using props to display values you need to display state values instead of them.You need to replace all the prop values with corresponding state values to make this code work properly. Just replace {props.date} to {date} {props.title} to {title} and {props.amount} to {amount}
I'm learning react at the moment and currently, making a todo app so that I can understand react more easily.
So here's what I'm trying to do:
The user clicks a button
The click fires a prompt which asks the user for the todo title (only title at the moment)
Then, that title is added to an array of all todos
And then, use that array to display each todo on the page
Code:
const [check, setCheck] = useState(false);
const [todo, setTodo] = useState([]);
function handleClick() {
let toAdd = prompt('Title: ')
setTodo([...todo, {
title: toAdd
}]);
}
useEffect(()=> {
if(todo.length !== 0) {
setCheck(true);
}
})
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr/>
{
check ?
todo.forEach((eachTodo)=> {
<TodoItems title={eachTodo}/>
})
: <span>Nothing</span>
}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
The const [check, setCheck] = useState(false); is written so that I can access the array if todo.length !== 0;
The problem comes in the rendering part. I can't figure out a way to display each and every todo in their own <TodoItems/> component, and also when using forEach(), nothing happens because I think that someone told me that setState() works asynchronously.
I really need some advice!
Thanks...
You are using
todo.forEach((eachTodo)=> {
<TodoItems title={eachTodo}/>
})
When you should be using
todo.map((eachTodo)=> {
return <TodoItems title={eachTodo}/>
})
Or
todo.map((eachTodo)=> (
<TodoItems title={eachTodo}/>
))
Also you have an infinite loop in your component:
useEffect(()=> {
if(todo.length !== 0) {
setCheck(true);
}
})
Each time the component updates, when the todo list isn't empty, you setCheck to true which triggers a new render.
Also, you don't need to use state for every variable, only the ones were a change should trigger a re-render.
Also your new todo-list state depends on the previous state so you should use a functional update.
https://reactjs.org/docs/hooks-reference.html
const [todoList, setTodoList] = useState([]);
function handleClick() {
let toAdd = prompt('Title: ');
setTodoList((prevTodoList) => [...prevTodoList, toAdd]);
}
const isTodoListEmpty = todoList.length === 0
return (
<div className="wholeContainer">
<div className="tododiv">
<span className="todos">Todos: </span>
<hr />
{!isTodoListEmpty ? (
todoList.forEach((todoItem) => {
<TodoItems title={todoItem} />;
})
) : (
<span>Nothing</span>
)}
</div>
<button className="add" onClick={handleClick}>
<i className="fas fa-plus"></i>
Add a Todo
</button>
</div>
);
I'm trying to rerender a list when I click a button that sends the first element of the array to the last position, however, when I click the button the component doesn't rerender, even thou the console.log shows that the array has changed:
codesandbox
import React, { useState } from "react";
const DailySchedule = () => {
const [exerciseList, setExerciseList] = useState([
"exercise 1",
"exercise 2",
"exercise 3"
]);
return (
<div>
<section>
<h2>Warm-up</h2>
<ul>
{exerciseList.map((exrcs, idx) => {
return (
<li>
{exrcs}{" "}
{idx === 0 && (
<button
onClick={() => {
exerciseList.push(exerciseList.shift());
setExerciseList(exerciseList);
console.log(exerciseList);
}}
>
Done
</button>
)}
</li>
);
})}
</ul>
</section>
</div>
);
};
export default DailySchedule;
Its because you're modifying the exerciseList array directly, which you shouldn't do, as the state update will see that the list is the same and not trigger a re-render. Instead make a copy of the array, and then use setExerciseList:
const newList = [...exerciseList]
newList.push(newList.shift())
setExerciseList(newList)
This issue is because array reference is not changed.
onClick={() => {
const list = [...exerciseList]
list.push(list.shift());
setExerciseList(list);
}}
This is because the array reference in state is not changed. Update the setState call like this,
<button
onClick={() => {
exerciseList.push(exerciseList.shift());
setExerciseList([...exerciseList]);
console.log(exerciseList);
}}
>
Done
</button>
You have to change the array reference to reflect that in the state.
setExerciseList([...exerciseList]);
Working code - https://codesandbox.io/s/react-playground-forked-ohl1u
As others have pointed out, you're mutating your state directly by using .push() and .shift(). This is a "no-no" in the react-world. Instead, you can treat your array as immutable by not changing your original state, but rather by producing a new array so that your state will update correctly. One approach to do this is to destructure the first item from your array and obtain the rest of your array in an array called rest, then set your new state using the destructured variables using the spread syntax (...):
onClick={() => setExerciseList(([first, ...rest]) => [...rest, first])}
This question already has answers here:
Push method in React Hooks (useState)?
(8 answers)
Closed 2 years ago.
Im working on a todo aplication in react using useState, im trying to save user input and then after they click submit push it to the listArray, later to display it...
I think im doing something wrong in the updateArray function, but I can seem to understand what.
import React, { useState } from "react";
function App() {
const listArray = [""];
const [list, updateList] = useState("");
function handleChange(event) {
const { name, value } = event.target;
updateList(value);
//console.log(list);
}
function updateArray() {
console.log(list);
listArray.push(list);
console.log(listArray);
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input name="entry" onChange={handleChange} type="text" />
<button>
<span onSubmit={updateArray}>Add</span>
</button>
</div>
<div>
<ul>
<li>{listArray[0]}</li>
</ul>
</div>
</div>
);
}
export default App;
There are several issues with your current code and I will briefly describe and provide a solution to fix them.
Your functions are working fine and as expected, but in a React application there are few ways to re-render a page or component and changing the local variable is not one of them. So you need to use a local state instead of local listArray variable. Since there is one state already you should either define another state or make your current state an object and put your component related states into it in one place I will go with the second approach, because it's more elegant and cleaner one.
const [state, setState] = useState({ list: [], input: "" });
After you define your state, you need to change them properly without effecting unnecessary ones. You just need to send the previous state, save it in the new one and only change the related state in each function. So with ES6 syntax, updating input state will look like this:
setState((prev) => ({ ...prev, input: value })); // immediate return "({})" of an object with iterating through the previous values "...prev" and updating input "input: value"
NOTE: You can read more about spread operator (...) here.
So your handle and updateArray function will look like this:
function handleChange(event) {
const { value } = event.target;
setState((prev) => ({ ...prev, input: value }));
}
function updateArray() {
setState((prev) => ({ ...prev, list: [...state.list, state.input] }));
}
onSubmit event will only work on forms or submit buttons so you need to change it to onClick. Also, to make the whole button catch the onclick action you need to set it on the button itself, instead of span element.
<button onClick={updateArray}> <!-- It's better to assign onclick action to an element with a function or arrow function to prevent it from running in the first render "(event) => updateArray(event)" -->
<span>Add</span>
</button>
And finally you need to map through the updated array of todo items in your JSX.
<ul>
{state.list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
Working Demo:
Save the current value into the state, and keep the list as well into the state so that it isn't cleared each render cycle.
import React, { useState } from "react";
function App() {
const [list, updateList] = useState([]);
const [currentValue, setCurrentValue] = useState()
function handleChange(event) {
setCurrentValue(event.target.value)
}
function handleClick() {
updateList([...list, currentValue])
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<div className="form">
<input name="entry" onChange={handleChange} type="text" />
<button type="button" onClick={handleClick}>
<span>Add</span>
</button>
</div>
<div>
<ul>
{list.map((res) => (
<li key={res}>{res}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
Also, moving the onClick to the button makes more sense semantically (and even for the UX) but it's up to you.
listArray is cleared with every rerender.
You should store your data in state. So
const [listArray, setListArray] = useState([])
And updateArray should look like:
function updateArray() {
setListArray([...listArray, list]);
}
I guess, in updateArray function should be some logic to clear list
Notice how listArray will always go back to the default value when your app component re-renders (using useState may re-render the component)
I would instead make the string the user inputs a normal const and use useState for the array so the values in the array will be saved across re-renders
I am using redux-form with a FieldArray.By default 1 element will be there in array and it is populated from JSON. I can add upto 3
elements in FieldArray component.
In below code, 'elementList'
property is coming from JSON and also I have store variables named
as'elements' and 'elementList'. I initialize these store variable with elementList
from JSON at first and then keep updating store variables when 'Add
Element' is clicked on. I can see store variables are updating
properly but on screen Field array elements are not updating.It may be because name property 'elementList' in FieldArray may refer to
JSON element.
Is it possible, if I can refer to store variables 'elementList' or 'elements' in name property
of 'FieldArray'. Please advice.
Main page
<div>
<FieldArray name="elementList" component={Elements}
props={this.props}/>
<button type="button" className="btn btn-primary"
onClick={event => this.addElement(elementDTO)}>Add Element
</button>
<br/>
</div>
addElement(elementDTO){
if(this.props.elements && this.props.elements!=undefined && this.props.elements.length >= 3){
return;
}
this.props.addReqElement(this.props.elements);
}
Field Array page
const elements= ({props, meta: {error, submitFailed}}) => {
const {fields} = props;
return (
{fields.map((element, index) => (
<div>
//Field definitions
</div>
))}
Thank you
Update:
Adding method from Redux Action and Reducer
export function addReqElement(childList) {
let state = store.getState()
let newChild=
state.requestReducer.DTOobj.requestDoc; //this is an empty element coming from backend with few properties and adding rest of the //proerties as below to create a new child
newChild.prop1 = null
newChild.prop2 = null
childList.push(newChild)
return (dispatch) => {
dispatch(setDoc(childList));
}
}
export function setDoc(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Update 2: I tried to remove push and used spread operator , but it did not work. I have inner array also, that is working as I am using different strategy. I take pull parent array ,it's index and update parent array with the new inner array. It works but parent array I am not getting how should I make it work. I tried to set the main array to the form props and render full page by dispatching an action but it did not work. Any suggestions plz?
From the main array page:
render() {
const {fields, parentArrayFromStore} = this.props;
return (
<div className="col-sm-12">
{fields.map((doc, index) => (
<div key={index}>
<div className="col-sm-12">
<FieldArray name={`${doc}.innerArrayList`} component={CustomInnerArrayComponent}/>
</div>
<div className="row">
<div className="col-sm-4">
<button type="button" className="btn btn-primary"
onClick={event => this.addInnerArray(index, parentArrayFromStore ,fields.get(index).innerArrayList)}>Add Printer
</button>
</div>
</div>
</div>
))}
</div>)
}
In Action class
export function addInnerArray(index, parentArrayFromStore, innerArrayList) {
let newInnerItem= {};
newInnerItem.prop1 = null
newInnerItem.prop2 = null
innerArrayList.push(newInnerItem)
parentArrayFromStore[index].innerArrayList = innerArrayList;
return (dispatch) => {
dispatch(setParentArray(parentArrayFromStore));
}
}
export function setParentArray(payload) {
return {
type: ADD_DOC,
payload: payload
}
}
Hi the issue is with the push statement in your function when updating states in the constructor or reducer use concat or spread operator[...]>
I have made a sample over here
please check
onAddItem() {
let list = [...this.state.items, {text:`Check 1`}];
this.setState({items:list});
}
in your case you can do the following
let arr = [...childList, newChild]
then dispatch the arr
dispatch(setDoc(arr));