Actually, I am stuck at a point, Please have a look the code once.
I want to push the user input into my array. Can anyone explain why it's throwing error.
import React, { useState } from 'react';
function Cart() {
const [item, setItem] = useState({ cart: ['Corn', 'Potato'] });
const saveInput = (e) => {
setItem({ input: e.target.value });
};
const addNewItem = () => {
const { cart, input } = item;
cart.push(input);
return setItem({ cart: cart });
};
return (
<div>
<input type="text" onChange={saveInput} />
<button onClick={addNewItem}>Add Item</button>
<ol>
{item.cart.map((subItems, sIndex) => {
return <li key={sIndex}> {subItems}</li>;
})}
</ol>
</div>
);
}
export default Cart;
You can use separate states. One for handle input and another for handle list:
import React, { useState } from "react";
function Cart() {
const [item, setItem] = useState(["Corn", "Potato"]);
const [input, setInput] = useState("");
const saveInput = (e) => {
setInput(e.target.value);
};
const addNewItem = () => {
const copyCart = [...item];
copyCart.push(input);
setItem(copyCart);
setInput("");
};
return (
<div>
<input value={input} type="text" onChange={saveInput} />
<button onClick={addNewItem}>Add Item</button>
<ol>
{item.map((subItems, sIndex) => {
return <li key={sIndex}> {subItems}</li>;
})}
</ol>
</div>
);
}
export default function App() {
return <Cart />;
}
Here's the full example:
Just like Dominik said, it would be much more efficient to have separate of your state.
If you want to make your existing code works:
Change
setItem({ input: e.target.value });
TO:
setItem({ ...item, input: e.target.value });
You forgot to spread your previous state before changing new ones.
import React, { useState } from "react";
function Cart() {
const [item, setItem] = useState({ cart: ["Corn", "Potato"] });
// have a new state to hold the input value
const [inputValue, setInputValue] = useState("");
const saveInput = (e) => {
setInputValue(e.target.value);
};
const addNewItem = () => {
// spread the exisiting cardlist and append the newly added input value
setItem((existingCartList) => ({
cart: [...existingCartList.cart, inputValue]
}));
// clear the input once it is added to card list
// so we can prevent the user from manually clearing it
setInputValue("");
};
return (
<div>
{/* need to pass the inputValue state to the value attribute
which makes the input a controlled component */}
<input type="text" value={inputValue} onChange={saveInput} />
<button onClick={addNewItem}>Add Item</button>
<ol>
{item.cart.map((subItems, sIndex) => {
return <li key={sIndex}> {subItems}</li>;
})}
</ol>
</div>
);
}
export default Cart;
Working Sandbox
Reference
Controlled Components
Related
I have a form in a page, when the user inputs the name of a new student and clicks submit, I want the content of that component (the form) to be completely replaced by the submitted name. How can I achieve this (Replace the form with the list onsubmit)?
I have read that I can use conditional rendering to toggle components, but it's not really clear to me how i can apply it here.
StudentListResult.Jsx
import React, { useState } from "react";
import StudentForm from "./StudentForm";
import StudentList from "./StudentList";
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
return (
<div>
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
/>
</div>
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
</div>
);
};
export default StudentListResult;
StudentListForm
import React from "react";
import { v4 as uuidv4 } from "uuid";
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
};
return (
<form onSubmit={addStudent}>
<div>
<input
value={newStudent}
type="text"
placeholder="Student Name"
onChange={(e) => setNewStudent(e.target.value)}
/>
</div>
<div>
<button>Submit</button>
</div>
</form>
);
};
export default StudentListForm;
StudentList.jsx
import React from "react";
const StudentList = ({ students = [], setStudentsList }) => {
return (
<div>
{students.map((student) => (
<ul key={student.id}>
<li>
<p>{student.name}</p>
</li>
</ul>
))}
</div>
);
};
export default StudentList;
So you want to show the form if not submitted and show the list if submitted? You can add a piece of state called submitted and do simple conditional rendering.
const StudentListResult = () => {
const [submitted, setSubmitted] = useState(false)
return (
{submitted ? <StudentList /> : <StudentListForm />}
);
};
And then in your addStudent function, set submitted.
const addStudent = (event) => {
// ...
setSubmitted(true)
}
If you want change form and list visibility state, you need pass custom function to form component:
StudentListResult.jsx:
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
const [getFormSubmitted, setFormSubmitted] = useState(false);
const setCompletedForm = () => {
setFormSubmitted(!getFormSubmitted);
};
return (
<div>
{getFormSubmitted ? (
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
) : (
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
onComplete={setCompletedForm}
/>
</div>
)}
</div>
);
};
Then call this function if form is submitted and all conditions is true
StudentListForm.tsx:
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
onComplete
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
onComplete();
};
So I was trying to update the value I got by the Addlist and I tried this but this isn;t working. Also when I click on the '+' button without writing anything, an empty list is created. How should I stop it. I've attached a code below.
import React from "react";
import "./App.css";
import { useState } from "react";
import TodoList from "./components/TodoList";
function App() {
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const updateList = (e) => {
setInput(e.target.value);
};
const AddList = () => {
console.log("value added")
setList((addValue) => {
return [...addValue, input];
});
setInput("");
};
const updateItems=(id)=>{
const newValue=[...list].map((newVal)=>{
if(input.id===id){
input.text='';
}
return newVal;
})
setList(newValue);
}
const deleteItems = (id) => {
console.log("deleted");
setList((addValue) => {
return addValue.filter((element, index) => {
return index !== id;
});
});
};
return (
<div className="todo-app">
<h1> Enter Anything</h1>
<input
type="text"
placeholder="Add anything"
value={input}
onChange={updateList}
/>
<button onClick={AddList}>+</button>
<ul>
{list.map((itemsvalue, id) => {
return (
<TodoList
itemsValue={itemsvalue}
key={id}
onSelect={deleteItems}
id={id}
onUpdate={updateItems}
/>
);
})}
</ul>
</div>
);
}
export default App;
Any kind of help would be appreciated. Also if I want to split this into multiple components is there a way to do.
When user clicks on the add button there is the check for empty String AddList method
for ex:- User updates second index value, second position value will get updated.
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [index, setIndex] = useState(null);
const updateList = (e) => {
setInput(e.target.value);
};
useEffect(() => {
setList(list);
console.log(list, '<>?');
}, [index]);
const AddList = () => {
if (input.trim() !== '') {
setList([...list, input]);
}
setInput('');
};
const updateValue = (index) => {
console.log(list[index]);
setIndex(index);
if (list[index].trim() !== '') {
setInput(list[index]);
}
};
const UpdateList = () => {
list[index] = input;
console.log(list, 'before <>?');
setIndex(null);
setInput('');
};
return (
<div>
<input type="text" placeholder="Add anything" value={input} onChange={updateList} />
<button disabled={!index && !list.length === 0} onClick={AddList}>
Add
</button>
<button disabled={input.trim() === ''} onClick={UpdateList}>
Update
</button>
{list.map((m, index) => (
<h1 style={{ border: '1px solid black' }} onClick={() => updateValue(index)}>
{m}
</h1>
))}
</div>
);
I have a number, n, that can be any value and I want to render n input fields while keeping track of each input's state but I'm having trouble figuring out how. For example, if n = 3, I want to render something like this:
<div>
<input onChange={(e) => setValue1(e.target.value)}/>
<input onChange={(e) => setValue2(e.target.value)}/>
<input onChange={(e) => setValue3(e.target.value)}/>
< /div>
In this example, I would manually need to create three states: value1, value2, value3. My goal is to have it dynamic so if in the future I change n to 4 or any other number, I don't have to manually create more states and mess with the component. Is there a good way to accomplish this using hooks?
You have to create a inputs state in order to track every input:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [inputs, setInputs] = useState(Array(10).fill(''));
const inputChangedHandler = (e, index) => {
const inputsUpdated = inputs.map((input, i) => {
if (i === index) {
return e.target.value;
} else {
return input;
}
});
setInputs(inputsUpdated);
};
return (
<div>
{inputs.map((input, i) => (
<input onChange={e => inputChangedHandler(e, i)} value={input} />
))}
</div>
);
}
You can check here, how things work:
https://stackblitz.com/edit/react-sdzoqh
You can create a new array with useState hook of size num that is passed from its parent and then using its index i.e. i you can change its input value using setValue function.
CODESANDBOX DEMO
Just for DEMO purpose and make the input come to a new line so I've wrapped it into div.
export default function App({ num }) {
const [arr, setValue] = useState(Array(num).fill(""));
console.log(arr);
function onInputChange(index, event) {
console.log(event.target.value);
setValue((os) => {
const temp = [...os];
temp[index] = event.target.value;
return temp;
});
}
return (
<div className="App">
{arr.map((n, i) => {
return (
<div key={i}>
<input onChange={(e) => onInputChange(i, e)} value={n} />
</div>
);
})}
</div>
);
}
Maybe I would create a custom hook to generate my inputs like
import React, { useState } from "react";
const CreateInput = (n) => {
const array = new Array(n).fill("");
const [valueInput, setValueInput] = useState({});
const handleChange = (event) => {
const { name, value } = event.target;
setValueInput({
...valueInput,
[name]: value,
});
};
const Input = array.map((_, i) => (
<input key={i} name={i} onChange={handleChange} />
));
return {
Input,
};
};
const Inputs = () => {
const { Input } = CreateInput(3);
console.log(Input);
return <div>{Input}</div>;
};
export default Inputs;
This could be done with an array in the state, with the values in the inputs. Initialize with empty strings
const [values, setValues] = useState(Array(n).fill(""))
const handleChange = (e, i) => {
const copy = values;
copy[i] = e.target.value
setValues(copy)
}
return (
<div>
{Array(n).map((x,i) => (
<input value={values[i]} onChange={e => handleChange(e,i)} />
))}
</div>
)
you can use useState([]) with array as default value for example
import "./styles.css";
import React, { useState } from "react";
export default function App() {
const n = 3;
const [values, setValues] = useState(new Array(n).fill(1, 0, n));
const handleChange = (i, value) => {
const v = [...values];
v[i] = value;
setValues(v);
};
const inputsRendrer = (values) => {
return values.map((v, i) => {
return (
<input
key={i}
value={values[i]}
onChange={(event) => handleChange(i, event.target.value)}
/>
);
});
};
return <div className="App">{inputsRendrer(values)}</div>;
}
new Array(n).fill(1, 0, n) // this create new array with values of 1 and length of n`
I am trying to create my first Todo list with React.js. I am trying to change the state from
const [todos, setTodos] = useState([])
To:
const [todos, setTodos] = useState({
todo: [],
isCompleted: false,
})
Just to try and add in a isCompleted state. However, when I change it, I get an error when running my application from a previously working map. The error is in the title.
Could somebody tell me what is wrong?
Code:
TodosApp.js
import React, { useState } from "react"
import Todos from "./Todos"
const TodoApp = () => {
const [todos, setTodos] = useState({
todo: [],
isCompleted: false,
})
const [input, setInput] = useState("")
const handleCurrentInput = (e) => {
setInput(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
console.log(input)
setInput("")
setTodos({
...todos,
task: input,
isCompleted: false,
})
}
const handleDelete = ({ index }) => {
const newTodos = [...todos]
newTodos.splice(index, 1)
setTodos(newTodos)
}
return (
<div id="todoForm">
<div class="container">
<div class="todo_form">
<div class="todo_input">
<form onSubmit={handleSubmit}>
<input
type="text"
id="input_todo"
onChange={handleCurrentInput}
value={input}
/>
</form>
<Todos todos={todos} handleDelete={handleDelete} />
</div>
</div>
</div>
</div>
)
}
export default TodoApp
Todos.js
import React, { useState } from "react"
const Todos = (props) => {
return (
<ul>
{props.todos.map((todo, index) => {
return (
<li key={todo}>
{todo}
<button onClick={() => props.handleDelete({ index })}>
Delete
</button>
</li>
)
})}
</ul>
)
}
export default Todos
You need to focus on each todo item including 2 props task, isCompleted instead of isCompleted of todos.
const [todos, setTodos] = useState([]);
var newTodo = {
task: 'React JS',
isCompleted: false
};
setTodos([...todos, newTodo]);
Then your todos's structure like below:
[
{
task: 'Study React JS',
isCompleted: false
},
{
task: 'Study React Redux',
isCompleted: false
},
];
Your state is an object containing an array of todos. This is what you're passing to your Todos component.
So you have two options:
Either pass todos.todos as a prop or
(Better way) Rethink your state. isCompleted seems that it should be part of each todo, because each todo should be completed not the list itself. A list is completed if every todo isCompleted
So your state would be const [todos, setTodos] = useState([])
I hope it's clear what I mean. Typing this from the phone is not so easy :-)
It's because you don't set to state the right way, todos got overwritten with the wrong value. You should write:
// handleSubmit
setTodos(s => {
...s,
task: input,
isCompleted: false,
});
and
// handleDelete
const newTodos = [...todos]
newTodos.splice(index, 1)
setTodos(s => ({ ...s, todos: newTodos }))
Working App: Stackblitz
import React, { useState, useEffect } from "react";
const TodoApp = () => {
/* initialize todos with array
instead of an object 👇*/
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
const handleCurrentInput = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
console.log(input);
/* update the state by appending an object having
key todo and isCompleted to copy of our main state,
todos.👇
*/
setTodos([...todos, { todo: input, isCompleted: false }]);
setInput("");
};
const handleDelete = ({ index }) => {
const newTodos = [...todos];
newTodos.splice(index, 1);
setTodos(newTodos);
};
useEffect(() => {
console.log(JSON.stringify(todos));
}, [todos]);
return (
<div id="todoForm">
<div class="container">
<div class="todo_form">
<div class="todo_input">
<form onSubmit={handleSubmit}>
<input
type="text"
id="input_todo"
onChange={handleCurrentInput}
value={input}
/>
</form>
<Todos todos={todos} handleDelete={handleDelete} />
</div>
</div>
</div>
</div>
);
};
export default TodoApp;
const Todos = props => {
return (
<>
<ul>
{props.todos.map((todo, index) => {
return (
<li key={index}>
{/**use null propogation to avoid accessing the null todo value which will not exist in first render. */}
{todo?.todo}
<button onClick={() => props.handleDelete({ index })}>
Delete
</button>
</li>
);
})}
</ul>
</>
);
};
isCompleted should be associated with each todo item.
So, you should use todos as array and store objects within that array. Each object will have isCompleted and a task property along with a unique Id as well.
const [todos, setTodos] = useState([]);
And your submit input would look like:
const handleSubmit = (e) => {
e.preventDefault();
const todo = {
task: input,
id: new Date().getTime().toString(),
isCompleted: false
};
const updatedTodos = [...todos, todo];
setTodos(updatedTodos);
console.log(updatedTodos);
setInput("");
};
Note: To generate unique Ids you can use uuid library. I have generated unique ids here using id: new Date().getTime().toString().
FULL WORKING CODE SANDBOX LINK: https://codesandbox.io/s/todosissue-2mc26?file=/src/TodoApp.js
Have modified handleDelete function as well :)
I would like to update the parent state from child component, which renders each object of the array of objects. The main goal of the child component is to update the original value from the array of objects.
I've the following code
Parent:
import { useState } from 'react';
import ExpenseItem from './expenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} {...expense} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
child:
import { useState, useRef } from 'react';
function ExpenseItem({ description, date, credit, debit }) {
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Once way, is to pass the parent state property (expenses) and the function that updates it (setExpenses) to the child Component via the props:
Parent:
import React from 'react';
import ReactDOM from 'react-dom';
import { useState } from 'react';
import ExpenseItem from './ExpenseItem';
function Update({ data }) {
const [ expenses, setExpenses ] = useState(data);
return (
<div>
Checking: { expenses[0].description } | { expenses[1].description }
<hr/>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button>Save</button>
</div>
);
}
export default Update;
Child:
import React from 'react';
import { useState, useRef } from 'react';
function ExpenseItem( props ) {
let { description, date, credit, debit } = props.expenses[props.index];
const setExpenses = props.setExpenses;
const [ edit, setEdit ] = useState(false);
const [ expenseDescription, setExpenseDescription ] = useState(description);
const textInput = useRef();
const renderDefaultView = () => {
return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
};
const renderEditView = () => {
return (
<div>
<input
type="text"
ref={textInput}
defaultValue={expenseDescription}
onDoubleClick={() => setEdit(true)}
/>
<button onClick={() => setEdit(false)}>X</button>
<button onClick={() => updateValue()}>OK</button>
</div>
);
};
const updateValue = () => {
const value = textInput.current.value;
setExpenseDescription(value);
textInput.current.defaultValue = value;
setEdit(false);
const expenses = [ ...props.expenses ]; // Get a copy of the expenses array
// Replace the current expense item
expenses.splice( props.index, 1, {
description: value, date, credit, debit
});
// Update the parent state
setExpenses( expenses );
};
return (
<div>
{edit ? renderEditView() : renderDefaultView()}
<span>{date}</span>
<p>{debit}</p>
<p>{credit}</p>
</div>
);
}
export default ExpenseItem;
Working demo
This can get really complicated as you move along, so the best option is to look for some sort of State Management solution, like using the Context API.
Also, take a look at this interesting post that talks about using the map index value as a key value: Index as a key is an anti-pattern