can't read else part - javascript

I want my divs to check/uncheck.
But, else part can't read my code (undefined)
Plz help me...
(First time click is fine
second time click is undefined)
import React, { useState } from "react";
import "./Blocking.css";
const Blocking = () => {
const [checked, setChecked] = useState([false, false, false, false, false]);
const onCheck = (e) => {
e.preventDefault();
console.log(e.target.getAttribute("name"));
if (checked[e.currentTarget.getAttribute("name")] === false) {
e.target.style.background = "aquamarine";
setChecked(!checked[e.target.getAttribute("name")]);
console.log("if " + checked[e.target.getAttribute("name")]);
} else {
e.target.style.background = "white";
setChecked(!checked[e.target.getAttribute("name")]);
console.log("else " + checked[e.target.getAttribute("name")]);
}
};
return (
<div className="container">
<div className="items" name="0" onClick={onCheck}>
1
</div>
<div className="items" name="1" onClick={onCheck}>
2
</div>
<div className="items" name="2" onClick={onCheck}>
3
</div>
<div className="items" name="3" onClick={onCheck}>
4
</div>
<div className="items" name="4" onClick={onCheck}>
5
</div>
</div>
);
};
export default Blocking;
Below code block is console.log in chrome)
[HMR] Waiting for update signal from WDS...
Blocking.jsx:9 2
Blocking.jsx:14 if false
Blocking.jsx:9 2
Blocking.jsx:18 else undefined
1st i click any blocks then, print fine.
2nd i click any blocks then, print undefined.

The error is in:
setChecked(!checked[e.target.getAttribute("name")]);
The "checked" array is being replaced by a boolean.
You could do it as:
let newChecked = [...checked]; //Destructure
let index = e.currentTarget.getAttribute("name") //Get index
newChecked[index] = !checked[index] //Toggle check
setChecked(newChecked)

Issues
You are mutating your checked state array. setChecked(!checked[e.target.getAttribute("name")]); mutates the state to a single boolean, so subsequent checked[e.currentTarget.getAttribute("name")] conditional tests will likely always be falsey.
You are also directly manipulating the DOM. This is an anti-pattern in React.
You need to shallow copy the entire array and update the specific index. Use the state value to derive the background color.
const onCheck = (e) => {
e.preventDefault();
const index = Number(e.currentTarget.getAttribute("name"));
setChecked(checked => checked.map((val, i) => i === index ? !val : val));
};
return (
<div className="container">
<div
className="items"
style={{ background: checked[0] ? 'white' : 'aquamarine' }}
name="0"
onClick={onCheck}
>
1
</div>
...etc
</div>
);

Related

How do I update a specific index

So I'm practising react with a simple task master app, where I add each user input as a new task in an array of tasks. In the app, I have Add, Delete, and Update buttons.
Everything seems to be working fine except for the update function, it updates the last index of the array instead of the specific index I clicked.
Here is my JSX
const JsxElement = task.map((eachTask, index) => {
return (
<Fragment key={index}>
<div key={index} className="table-data-container">
<div className="item-data">{eachTask}</div>
<div className="item-data">{date}</div>
<div className="item-data">
<div className="btn-data-container">
<div className="btn-data">
<div className="btn" onClick={() => deleteTask(index)}>Delete</div>
</div>
<div className="btn-data">
<div className="btn" onClick={() => UpdateTaskBtn(eachTask, index)}>Update</div>
</div>
</div>
</div>
</div>
<br/>
{task.length - 1 === index &&
<div className="input-update-container">
<div className="input-area">
<input
ref={inputRef}
type="text"
/>
</div>
<div className="btn-update-add-container">
{update ?
<div className="btn-add" onClick={() => handleTaskUpdate(eachTask, index)}>Update
Task</div>
:
<div className="btn-add" onClick={handleTask}>Add Task</div>
}
</div>
</div>
}
</Fragment>
)
})
The first update button function prepares the input, sets the task to be updated and makes the update button visible. The second one is where I want the update action to happen once clicked.
function UpdateTaskBtn(eachTask) {
inputRef.current.value = eachTask
setUpdate(true)
}
function handleTaskUpdate(e, index) {
const list = [...task]
list[index] = inputRef.current.value
setTask(list)
inputRef.current.value = ""
setUpdate(false)
}
I want to be able to set the task to the specific index I want to update.
based on this line of code
{task.length - 1 === index &&
you are checking if the index is the last index , so you are passing the last index to the handleTaskUpdate function. so you can define a updateIndex state
const [updateIndex,setUpdateIndex] = useState()
then your function should look like this
function UpdateTaskBtn(eachTask,index) {
inputRef.current.value = eachTask
setUpdateIndex(index)
setUpdate(true)
}
function handleTaskUpdate(e, index) {
const list = [...task]
list[updateIndex] = inputRef.current.value
setTask(list)
inputRef.current.value = ""
setUpdate(false)
}
do not forget to pass index to UpdateTaskBtn function in onClick event
Try this
function handleTaskUpdate(e, index) {
const value = inputRef.current.value
const updatedTasks = task.map((task, i) => i === index ? value : task)
setTask(updatedTasks)
inputRef.current.value = ""
setUpdate(false)
}

Cannot read the array property. Objects are not valid as a React child

I am following a tutorial exercise and I got the following error
Objects are not valid as a React child
I know this error is related to the object as I am trying to access the object but it needs an individual item of an object but not sure.
Why cannot the map loop over each item in the array?
Following is my code
var template = <h1>Indecision App</h1>;
var app = {
title: 'Indecision App',
subtitle: 'yo',
options: []
}
let count = 0;
function checkSubtitles (subtitle){
if(subtitle){
return <p>{subtitle}</p>
}else{
return undefined
}
}
function reset(){
count = 0;
reRenderApp();
}
function increaseCount(){
count++;
reRenderApp();
}
function onSubmitHandle(e){
e.preventDefault();
const options = e.target.elements.options;
app.options.push(options);
reRenderApp();
e.target.elements.options.value = ''
}
function removeAll(){
app.options = [];
reRenderApp();
}
function reRenderApp(){
var templateTwo = (
<div>
<h1>{app.title}</h1>
{checkSubtitles(app.subtitle)}
<p>Count: {count}</p>
<p>Array Length: {app.options.length > 0 ? app.options.length : '0 Items'}</p>
<ol>
{app.options.map((item)=>{
return <li key={item}>{item}</li>
})}
</ol>
<hr></hr>
<form onSubmit={onSubmitHandle}>
<input type="text" name="options" />
<input type="submit" value="Push to the Array" />
<input type="reset" value="Empty my list" onClick={removeAll} />
</form>
<button onClick={()=>{
increaseCount();
}}>Increase Count</button>
<button onClick={()=>{
reset();
}}>Reset Count</button>
</div>
)
ReactDOM.render(templateTwo, appRoot)
}
var appRoot = document.getElementById('app');
reRenderApp();
<body>
<div id="app"></div>
<script src="https://unpkg.com/react#16.0.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.0.0/umd/react-dom.development.js"></script>
<script src="./app.js"></script>
</body>
</html>
The main problem is, as you mentioned: Objects are not valid as a React child
But, what is happening?
If we go into:
function onSubmitHandle(e){
e.preventDefault();
// Line 1
const options = e.target.elements.options;
app.options.push(options);
reRenderApp();
// Line 2
e.target.elements.options.value = ''
}
So in Line 1, you're pushing options into the options array.
But, then in Line 2, we can notice options has an attribute (so, it's an object)
So, if you change Line 1, from:
const options = e.target.elements.options;
To this:
const options = e.target.elements.options.value;
It'd work.
Also, to check what I'm saying you have 2 options:
option 1: console.log
function onSubmitHandle(e){
e.preventDefault();
const options = e.target.elements.options;
console.log({ options })
app.options.push(options);
reRenderApp();
e.target.elements.options.value = ''
}
option 2: make that option a valid child of react with JSON.stringify()
<ol>
{app.options.map((item, index)=>{
return <li key={index}>{JSON.stringify(item)}</li>
})}
</ol>
You can do
{app.options.length && app.options.map((item)=>{
return <li key={item}>{item}</li>
})}
But you must be sure that "item" here is not an object as you can't render an object
The reason for this is that your options array is going to be filled with elements as you're pushing the input element with the name of "option" into your array - this elements are objects in JS which you can't render out as list items.
Use React State to store anything that's going to change in the UI - in this case your list of options> So rather than doing
var app = {
title: 'Indecision App',
subtitle: 'yo',
options: []
}
let count = 0;
Do:
const [options, setOptions] = React.useState([]);
const [count, setCount] = React.useState(0);
Title and subtitle are probably not going to change, so just put them in h1 & h2 elements - if they are, then use the state pattern again.
Get rid of the two inputs with types of "submit" & "reset" just use normal button elements instead.
You'll also need an onchange event on your input where the text will go in and each time the onchange event is fired (i.e, when a user types) you'll need to save the input text
const [inputText, setInputText] = React.useState('');
const handleChange = (e) => {
const {value} = e.target;
setInputText(value)
}
<input type="text" value={inputText} onChange={handleChange}/>
Then in your onHandleSubmit function, just have
const onHandleSubmit = () => {
setOptions([...options, inputText]);
setInputText('')
}
This should work

Toggle correct answer message with radio button in React

Given a small quiz app, I'm trying to toggle a message under the questions to show the user that they have selected the correct answer or incorrect answer.
I have a small message that I'm gating with a boolean value which is set by the selection of the radio button (though the correct radio button isn't getting selected on initial click for some reason, only incorrect answers are), and need it to show a message of "You got it right" if the answer is true or "incorrect" if false. I can show true, but the logic isn't working to show false if the answer is incorrect and then clear the messaging when the "next question" button is clicked.
function App() {
let [points, setPoints] = useState(null);
let [counter, setCounter] = useState(null);
let [question, setQuestions] = useState();
let [isCorrect, setIsCorrect] = useState(false); <==Store correct answer selected
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
type="radio"
name="answer_group"
className="answer"
value={props.answer}
onChange={checkAnswer}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
displayCorrect(ans.correct);
//Toggles message to true if answer is correct
ans.correct === true ? setIsCorrect(true) : setIsCorrect(false);
}
function Quiz(props) {
return (
<div className="quiz">
<div className="quesiton" role="h2">
{props.question}
</div>
<ul className="answers">{props.children}</ul>
</div>
);
}
function displayCorrect(correct) {
let correct_msg = correct ? "correct" : "incorrect";
console.log("Answer was " + correct_msg);
}
function nextQuestion() {
setIsCorrect(false); <== Should hide message when user selects "next question" button
if (document.querySelector('input[name="answer_group"]:checked') == null) {
alert("Must select an answer before proceeding to the next question");
return;
}
let val = document.querySelector('input[name="answer_group"]:checked')
.value;
let answerObj = question[counter].answers.filter(
(ans) => ans.value === val
)[0];
let updated_points = answerObj.correct ? points + 1 : points;
setPoints(updated_points);
let nextQuestion = counter + 1;
if (counter < question.length - 1) {
setCounter(nextQuestion);
} else {
setCounter(0);
}
displayCorrect(answerObj.correct);
}
return (
<div className="Quiz slide-top">
{!is_started ? (
<div className="start-intro-wrapper">
<h1 className="name">Quiz App</h1>
<Starter start={start} />
</div>
) : (
<div className="quick-wrapper slide-in-bottom">
<Quiz question={question[counter].question}>
{question[counter].answers.map((answer, index, arr) => {
return (
<Answer
key={index}
index={index}
answer={answer.value}
correct={answer.correct}
/>
);
})}
</Quiz>
<div className="answer-controls">
{isCorrect === true ? <p>You got it right!</p> : null} <== Messaging should show if user has selected correct message if correct option is selected
<button onClick={(e) => nextQuestion()}>
Next question
</button>
</div>
</div>
)}
</div>
);
}
Codesandbox Link
It doesn't display anything because you put null in the other condition for the display.
If you replace it with {isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>} the correct message is displayed.
You can also remove completly the function displayCorrect it's only writing in the console.
If you want to clear the message when the user click on "next question" you could do it with a new variable hasAnswered set at false by default.
You set it to true in your checkAnswer function, and to false in your nextQuestion function.
The code for the text at the bottom would then looks like:
{hasAnswered &&
(isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>)
}
Regarding the issue with you needing to click twice to update the radio button selected, i think it's a re-render issue when isCorrect change.
To avoid the issue, you could store the value checked by the user and use this to display if the radio should be checked or no.
It works with the following changes
let [isChecked, setChecked] = useState(null);
[...]
function Answer(props) {
return (
<li aria-labelledby="answers-list">
<label>
<input
checked={isChecked===props.answer}
type="radio"
name="answer_group"
className="answer"
value={props.answer}
onChange={checkAnswer}
/>
{props.answer}
</label>
</li>
);
}
function checkAnswer(e) {
let val = e.target.value;
let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
setIsCorrect(ans.correct);
setChecked(ans.value);
}
You also need setChecked(null) in your nextQuestion function to reset everything.

React - Last list element removed when deleting from the middle

I am trying to implement a list composed of an <input> field as well as a delete button, for each item. I also have an Add item button near the list so the user can add new entries.
I am encountering an issue when trying to delete a certain item from the list.
For example:
The initial list
index
input-value
1
a
2
b
3
c
4
d
5
e
After deleting the item at index 3
index
input-value
1
a
2
b
4
c
5
d
As you can observe, the right item is deleted, but the contents are somehow shifted from their initial place and it appears like the last item is always deleted.
I can't figure it out what am I doing wrong.
Here is the parent component:
export default function ListPage() {
const [itemList, setItemList] = useState([]);
const removeItem = (index) => {
console.log(index);
const list = Object.assign([], itemList);
const updatedList = list.filter((object, i) => i != index);
setItemList(updatedList);
console.log(updatedList);
};
const addItem = () => {
const updatedList = [...itemList, <ListElement />];
setItemList(updatedList);
console.log(updatedList);
};
return (
<div className="list-page">
<div className="list-page-title">
<h2>Create your own List</h2>
<br></br>
<h4>Create your own list</h4>
</div>
<input
className="title-input"
type="text"
placeholder="Enter Title"
/>
<div className="list-content">
{itemList.map((data, index) => {
return (
<div>
<ListElement
handleRemove={() => removeItem(index)}
index={data.index}
/>
</div>
);
})}
</div>
<button className="add-button" onClick={addItem}>
<span className="list-page-button-icon">+</span>Add Item
</button>
</div>
);
}
And here is the ListElement component:
export default function ListElement(props) {
return (
<div className="list-item">
<input
className="item-input"
type="text"
placeholder={props.index}
></input>
<MdRemoveCircleOutline
className="remove-button"
onClick={props.handleRemove}
/>
</div>
);
}
Inside your removeItem function, you don't need to do const list = Object.assign([], itemList);; which is messing the order of how the list is stored and rendered.
You already have access to the entire list in the itemList variable you defined before.
So, instead of
const removeItem = (index) => {
console.log(index);
const list = Object.assign([], itemList);
const updatedList = list.filter((object, i) => i != index);
setItemList(updatedList);
console.log(updatedList);
};
So, instead of
const removeItem = (index) => {
const updatedList = itemList.filter((object, i) => i != index);
setItemList(updatedList);
};

select tag is keeping initial state value and not updating

I'm having some issues updating a select tag value. When I click on it and try changing the value it logs the new value to the console, but it's value is not changing no matter how many times I click a different option. But when I make some changes in the code editor and save it it display the new value but if I try againg changing the value clicking on it it does not change.
const quantityChange = (e) => {
console.log('changing quantity')
const newCart = shoppingCart
console.log(
"new cart variable created. It's value is " + JSON.stringify(newCart)
)
const index = e.target.dataset.index
console.log('Item index is ' + index)
newCart[index]['quantity'] = e.target.value
console.log(
'The new quantity for the item is ' + newCart[index]['quantity']
)
setShoppingCart(newCart)
console.log(
'new value set for shopping cart. ' +
JSON.stringify(shoppingCart)
)
}
const ShoppingCart = ({ shoppingCart, quantityChange, removeFromCart }) => {
const onSubmit = (e) => {
e.preventDefault()
}
if (shoppingCart.length === 0) {
return <div>There's no items in the shopping cart</div>
}
const options = []
for (let i = 0; i <= 10; i++) {
options.push(
<option name="quantity-1" value={i} key={'qsfkajlf' + i}>
{i}
</option>
)
}
return (
<div>
<h1>
There's {shoppingCart.length} item{shoppingCart.length > 1 && 's'} in
your shopping cart.
</h1>
{shoppingCart.map((item, index) => {
const { id, name, price, image, quantity } = item
return (
<div key={id}>
<div className="flex-container">
<div className="img-container">
<img src={image} alt="" />
</div>
<div className="item-info">
<h2>{name}</h2>
<p>${price}</p>
</div>
<div className="remove-quantity">
<div className="quantity">
<form onSubmit={onSubmit}>
{/* TODO: don't allow more than 10 items */}
<select
id="quantity"
data-index={index}
onChange={quantityChange}
value={quantity}
>
{options}
</select>
</form>
</div>
<div className="remove-container">
<button data-remove={index} onClick={removeFromCart}>
x
</button>
</div>
</div>
</div>
</div>
)
})}
</div>
)
}
It isn't updating because you are doing this:
const quantityChange = (e) => {
console.log('changing quantity')
const newCart = shoppingCart // <-- copy a reference to shoppingCart
// do stuff
setShoppingCart(newCart) // <-- write the original (modified) object back in
}
As the object reference doesn't change, the re-render isn't triggered.
Instead, you just need to copy your object (instead of just copying the reference):
const quantityChange = (e) => {
console.log('changing quantity')
const newCart = {...shoppingCart} // <-- copy the contents into a new object

Categories