Generate a new item in an array in React? - javascript

I'm building a to-do list and I want each list item to have a number, starting from 1. I'm using the useState hook on the counter but I can't figure out how to add new items to the array every time I click on a button. I haven't coded in months and I'm really rusty.
function Product() {
const [input, setInput] = useState("");
const [todo, setTodo] = useState([]);
const [count, setCount] = useState([]);
const addTodo = e => {
e.preventDefault();
setTodo([...todo, input]);
setCount(prevCount => [...prevCount, prevCount + 1]);
setInput("");
};
return (
<div>
<form>
<input value={input} onChange={e => setInput(e.target.value)} type='text' />
<button type='submit' onClick={addTodo}>
Add!
</button>
</form>
<h2 style={{ marginBottom: "0.5rem" }}>List of To-dos !</h2>
{todo.map(todo => (
<p>
{count.map(count => (
<p>{count}</p>
))}
{todo}
</p>
))}
</div>
);
}
I want to make it so each time I add a list item, it adds its number to the left. The first item would have 1, the second 2, etc..

This is really bad practice:
What you want to do is use the index param that Javascript map function gives you:
Good practice
todo.map((todo, index) => (
<p>{index} - {todo}</p>
))
Output
0 - walk dog
1 - do yoga
Now if want the index to start at 1, you can simply add +1 to index
todo.map((todo, index + 1) => (
<p>{index} - {todo}</p>
))
Output
1 - walk dog
2 - do yoga
Since the index values are unique, you could use that to your benefit when performing other actions such as deleting etc. Usually you add the key attribute to the individual child values according to the official React documentation as follows
todo.map((todo, index + 1) => (
<p key={index + 1}>{index} - {todo}</p>
))
where key is a unique value.
Also, change your variable names to something more meaningful.
I'd suggest changing todo to plurial todos.
Your final code should look like this:
function Product() {
const [input, setInput] = useState("");
const [todo, setTodo] = useState([]);
const addTodo = e => {
e.preventDefault();
setTodo([...todo, input]);
setInput("");
};
return (
<div>
<form>
<input value={input} onChange={e => setInput(e.target.value)} type='text' />
<button type='submit' onClick={addTodo}>
Add!
</button>
</form>
<h2 style={{ marginBottom: "0.5rem" }}>List of To-dos !</h2>
{todo.map((count, index + 1) => (
<p key={index + 1}>{index} {todo}</p>
))}
</div>
);
}

Don't.
There's no reason to track the count of items in an array as its own separate state value.
Arrays have a length property which tells you the count of items in the array.
.map() includes an index in the callback, so if you just want to output the array index then you can do that.
You certainly don't need an array of numbers from 1-X in order to know the numbers from 1 to X. The value of X alone gives you this information.
Remove the count state value entirely, and just output the index of the array from the todo list:
{todo.map((t, x) => (
<p>
{x}
{t}
</p>
))}
Note also that I abbreviated your inner todo variable to just t. You can call it whatever you like, but giving it the same name as another variable you already have is just asking for confusion and bugs.
You may instead want to rename your array to something plural, like todos. Then each item therein is semantically a todo:
{todos.map((todo, x) => (
<p>
{x}
{todo}
</p>
))}
Basically, names are important. A variable/type/property/etc. name should tell you exactly and unambiguously what it is. Poor variable names lead to confusing code, and being confused about your code is exactly what brought you here.

Related

What should I use as a key for "row" elements in react?

I have a gallery that displays a number of books per row. This gallery takes an array of books as a prop and uses "itemsPerRow" prop to chunk the books into a 2 dimensional array and then loops through all the books to display the books in a grid-like structure.
export default function Gallery({ items, itemsPerRow, renderLink }) {
itemsPerRow = itemsPerRow ?? 3
const rows = chunk(items, itemsPerRow)
const renderEmptyItems = (itemsToRender) => {
const items = []
for(let n = itemsToRender; n > 0; n--) {
items.push(<GalleryItem key={`empty_${n}`} empty/>)
}
return items
}
return (
<div>
{
rows.map((row, index) => (
<div key={index} className="tile is-ancestor">
{row.map(item => <GalleryItem key={item.id} renderLink={renderLink} {...item}/>)}
{/* Add empty gallery items to make rows even */}
{index + 1 === rows.length && renderEmptyItems(itemsPerRow - row.length)}
</div>
))
}
</div>
)
}
However, unless I give each div representing a row a key, react complains about the lack of keys. As I understand it, using the index as a key doesn't really help react and should be avoided. So what should I use as a key here <div key={index} className="tile is-ancestor"> instead of the index?
Use a unique identifier (book.id, maybe book.title if it's unique) for the key props. If your data does not have a unique identifier, it's okay to use index.
You need to specify a value that uniquely identify the item, such as the id. You can read more about keys in the documentation.
Also it is not recommended to use indexes as keys if the order of your data can change, as React relies on the keys to know which components to re-render, the documentation I linked explains that further.
You can use the unique_identifier which differentiate each of the documents(probably, you should pass the document _id as a key prop in the row components)
<div className="row">
{notes.map((item) => {
return (
<div key={note._id} className="col-md-6">
<Component item={item} />
</div>
);
})}
</div>

REACT: How to swap elements in To Do list by their priorities

I'm doing to do list and I want to make function that swap tasks by their priorities. For example:
Go to gym
Learn react
I want to make button that move elements up and down and get:
Learn react
Go to gym
I have function that I'm pretty sure working correct but I think problem in <div> where I use this function
const moveUpDown = (currentIndex, nextIndex) =>{
const newCounts = [...todos]
const currentCounts = newCounts[currentIndex]
const previousCounts = newCounts[nextIndex]
newCounts[currentIndex] = previousCounts
newCounts[nextIndex] = currentCounts
setTodos(newCounts)
}
This is my return:
function ToDo(props) {
return (
<div key={props.todo} className="item-todo">
<div
className={props.todo.complete ? "item-text strike" : "item-text"}
onClick={() => props.toggleTask(props.todo.id)}
>
{props.todo.task}
</div>
<div className="item-delete" onClick={() => props.removeTask(props.todo.id)}>
X
</div>
// Lines below I'm using function moveUpDown
<div
className="item-moveUpDown" disabled = {props.todo === 0} onClick={() => props.moveUpDown(props.todo, props.todo - 1)}
>
Up
</div>
<div
className="item-moveUpDown" disabled = {props.todo === 0} onClick={() => props.moveUpDown(props.todo, props.todo + 1)}
>
Down
</div>
</div>
)
}
The parameters you're passing to moveUpDown don't make sense - it looks like they should be numbers, but you're passing props.todo which is an object.
Maybe if you changed the moveUpDown function to accept the "to do" you want to move, and a delta to indicate which direction you wanted to move it; eg:
const moveUpDown = (todo, delta) =>{
const newCounts = [...todos];
// Remove from the array
const currentIndex = newCounts.indexOf(todo);
newCounts.splice(currentIndex, 1);
// Now put it back in at the new position
newCounts.splice(currentIndex + delta, 0, todo);
setTodos(newCounts)
}
Now you can change your callers, eg:
<div
className="item-moveUpDown"
disabled={props.todo === 0}
onClick={() => props.moveUpDown(props.todo, 1)}
>
Down
</div>
Note also that your disabled attribute doesn't make sense - props.todo is an object so it will never be equal to zero. Perhaps you should be passing the index of the todo item as a separate property?

How to loop through new input boxes renderer in React and get the values in an array?

I need to dynamically generate multiple divs with a single input-box in it, so the user can add a number.
The user can add by clicking a button, any number of divs with input-box to put a number in it.
After the user end with the entry of the data, must click a button to process the data.
I've find out how to use React to iterate through an existing array, but not about how to iterate through a new DOM tree that was created dynamically by the user, and then generate an array with the values inside all the input-boxes.
After processing, different values will be displayed (max value, min value, average, return results from equations, etc)
Without seeing your code it's hard to help, but you probably want to use controlled inputs rather than uncontrolled ones, and so you'd have an array of the current values for the inputs as state information.
For instance, in a functional component using hooks:
const { useState } = React;
function Example() {
// The values we use on the inputs
const [values, setValues] = useState([]);
// Update the value at the given index
const updateValue = (value, index) => {
setValues(values =>
Object.assign([], values, {[index]: value})
);
};
// Add an input
const addInput = () => {
setValues(values => [...values, ""]);
};
// Get the sum (just an example; and one of the very few places I'll use `reduce`)
const sum = values.reduce((sum, value) => sum + Number(value), 0);
// Render as many inputs as we have values, along with the
// button to add an input and the sum
return (
<div>
<div>
{values.map((value, index) =>
<div key={index}>
<input type="text" value={value} onChange={evt => updateValue(evt.target.value, index)} />
</div>
)}
</div>
<div>Sum: {sum}</div>
<input type="button" value="Add Input" onClick={addInput} />
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
I think you could just create one div, and an array to store the values, then create a function that everytime the user choose to add a new value, it saves the current on that array and clean the input. So, when the user select to process the data it takes that array and do what you need.

index.js:1 Warning: Encountered two children with the same key, `index`. Index is unique

I am trying to assign index to key of dynamic list component but it gives warning of duplicate keys. Here is the code below
function App() {
const [userInput, setUserInput] = useState('');
let calcLengthHandler = (event)=>{
setUserInput(event.target.value);
}
let charArr = [...userInput.split('')];
let charCmp = ( <div>
{
charArr.map((character,index)=>{
return (
<CharComponent onClick={deleteCharCmp} char={character} key='index'></CharComponent>
)
})
}
</div>)
return (
<div className="App">
<div className='container'>
<h1 className='container-output'>Dynamic List</h1>
<input type='text' className='user-input' onChange={calcLengthHandler} value={userInput} placeholder='Enter user input'></input>
<p className='container-output inline'>Length of user input: {userInput.length}</p>
<ValidationComponent length={userInput.length}></ValidationComponent>
{charCmp}
</div>
</div>
);
}
Every time a user enters some string, I am trying to create a component with each character. I have further functionality to achieve which I have not included.
On adding debugger just before .map function, I see its called twice which may be causing the issue. I guess 1. when the component is initialized and 2. when I call {charCmp}. How can I resolve duplicate keys problem here?
This sets the key attribute to the string 'index':
key='index'
This sets the key attribute to the value of the index variable:
key={index}

The quiz does not block the questions answered

The quiz generally works ok, but when I click 2 times in the same answer, I count the points 2 times, like I would click 100 times
in the same correct answer I have 100 points. I don't know how to fix it .. Please help ...
QuestionBox:
const QuestionBox = ({ question, options, selected }) => {
const [answer, setAnswer] = useState(options);
return (
<div className="questionBox">
<div className="question">{question}</div>
{(answer || []).map((text, index) => (
<button key={index} className="answerBtn" onClick={() => {
setAnswer([text]);
selected(text)
}}>{text}</button>
))}
</div>
)
}
computeAnswer:
computeAnswer = (answer, correctAnswer) => {
if (answer === correctAnswer) {
this.setState({
score: this.state.score + 1,
})
render:
{this.state.qBank.map(
({ question, answers, correct, id }) => (
<QuestionBox key={id} question={question} options={answers} selected={Answers => this.computeAnswer(Answers, correct)} />
)
)}
You can have a boolean flag in QuestionBox state, initialized to false, and switched to true on the first click, then bypass score calculation if this flag is true :
const QuestionBox = ({ question, options, selected }) => {
const [answer, setAnswer] = useState(options);
const [alreadyAnswered, setAlreadyAnswered] = useState(false);
return (
<div className="questionBox">
<div className="question">{question}</div>
{(answer || []).map((text, index) => (
<button disabled={alreadyAnswered} key={index} className="answerBtn" onClick={() => {
if(!alreadyAnswered) {
setAnswer([text]);
selected(text);
setAlreadyAnswered(true);
}
}}>{text}</button>
))}
</div>
)
}
I also add disabled attribute if question has already been answered to let
user knows that this is one-time only.
Also it would be a good idea to put the onClick logic in a function to improve performance (https://medium.com/#Charles_Stover/cache-your-react-event-listeners-to-improve-performance-14f635a62e15).
By the way you should avoid to init state with props : React component initialize state from props
Currently, answer will either be an array of possible answer strings, or an array containing the single answer chosen by the user. You could change it so that the click listener is only attached if the length of the answer is greater than 1:
<button key={index} className="answerBtn" onClick={answer.length === 1 ? null : () => {
// rest of the code
This way, once the user chooses an answer, further clicks on the (now single rendered) button won't do anything, and the score will only be (possibly) incremented the first time the button is clicked.
answer is a bit of a strange variable name for a collection of answer strings - perhaps rename it to answerOptions or possibleAnswers or something like that, for better readability.

Categories