I'm mapping an array of objects as shown below
<div>
{categoryArray.map(category => <DFCard category={category} key={category.id}/>)}
</div>
DFCard
<div className="cat_div" id={category.id} onClick={() => this.selectCategory(category.id)}>
<i className={`${category.icon} fontawesome vertical_center setting_icon`}/>
<span className="cat_lbl vertical_center">{category.name}</span>
{this.state.selectedId === category.id && 'selected'}
</div>
selectCategory(id) {
this.setState({
selectedId: id
})
}
I'm trying to show a selected on the item i'm clicking on. My approach does not make the selected label to remove when clicking on another list item. How can i fix this issue?
try this
selectCategory(id) {
let oldSelected = this.setState.selectedId;
oldSelected.push(id)
this.setState({selectedId: oldSelected})
}
{this.state.selectedId && this.state.selectedId.indexOf(category.id) > -1 && 'selected'}
Related
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)
}
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.
I want to display a div layer if list if empty into React page. I tried this:
{(() => {
if (!ordersList && ordersList.length === 0) {
return (
<div className="alert alert-warning">No Orders found</div>
)
}
})()}
But it's not working. What is the proper way to implement this?
The condition is incorrect. If list is empty, then !ordersList will be false.
So, the <div> won't be rendered.
You may try this:
{(() => {
if (!ordersList || ordersList.length === 0) {
return (
<div className="alert alert-warning">No Orders found</div>
)
}
})()}
Or even simpler:
{
(!ordersList || ordersList.length === 0) &&
<div className="alert alert-warning">No Orders found</div>
}
If I understand you correctly, you want to conditionally render a div when ordersList (a variable/state that I presume is in your component) is empty. Then add this to your JSX returned by your component.
{!ordersList.length && <div className="alert alert-warning">No Orders found</div>}
I am working on a project as a means to practice some stuff in react and I need to render a button for each of the map data. I did this successfully but expand and collapse has been giving me issue. Whenever I click on the button all data collapse and expand together.
const DataFetch = () => {
...
const [btnValue, setBtnValue] = useState('+');
const handleChange = (e) => {
setShowData(!showData);
setBtnValue(btnValue === '+' ? '-' : '+');
};
return (
<div className='container'>
...
{studentResults
.filter((val) => {
if (searchTerm === '') {
return val;
} else if (
val.firstName.toLowerCase().includes(searchTerm.toLowerCase()) ||
val.lastName.toLowerCase().includes(searchTerm.toLowerCase())
) {
return val;
} else {
return null;
}
})
.map((student) => {
return (
<div key={student.id}>
<div className='card'>
<div className='row'>
<div className='col-2'>
<div className='pic'>
<img src={student.pic} alt='avatar' />
</div>
</div>
<div className='col'>
<div className='details'>
<p className='name'>
{student.firstName.toUpperCase()}{' '}
{student.lastName.toUpperCase()}
</p>
<div className='sub-details'>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<p>
Average:{' '}
{student.grades.reduce(
(a, b) => parseInt(a) + parseInt(b),
0
) /
student.grades.length +
'%'}
</p>
<button onClick={handleChange} className='showBtn'>
{btnValue}
</button>
{showData && (
<div>
<br />
{student.grades.map((grade, key) => {
return (
<p key={key}>
Test {key + 1}: {grade}%
</p>
);
})}
</div>
)}
...
Collapse Image
Expand Image
All the elements expand and collapse together because you assign to all of them the same state showData state.
One solution would be to add a new field to your data (so inside student) that is true or false when you want to expand or collapse the single student.
Another solution would be to create the showData state as an array where each element correspond to a different student. When you click the button, in this case, you pass to the function for example the id and with that you link your student to the right element inside the showData.
I want to remove an item from an array getting the target.id, this app is for a cart, so in other words i want to remove an item from the cart when i click on the bin icon.
<span style={{fontSize:'50px'}}>Basket</span>
<ul style={{listStyle: 'none'}}>
{this.state.clickedNum.map((i) => <li key =
{this.props.gameData.id[i]} value={i}>
{this.props.gameData.gameNames[i]} <br/>
<img src={require('../assets/coins.png')} style={{width:'20px'}}/>
<strong> {this.props.gameData.prices[i]} Gil </strong>
<img src={require('../assets/bin.png')} style={{width:'20px', justifyContent:' flex-end'}} id={this.props.gameData.id[i]} onClick={this.removeClick}/>
<br/>
<hr style={{borderColor:'black'}} />
</li>
</ul>
This is the onClick={this.removeClick} function:
removeClick = (e) => {
e = e.target.id;
let clickedNum = this.state.clickedNum;
let isDisabled = this.state.isDisabled;
console.log("this is i" + e);
clickedNum.splice(e, 1);
isDisabled.splice(e,1);
this.setState({
clickedNum: clickedNum,
isDisabled:isDisabled
})
this.forceUpdate();
}
remove click is binded like this in the constructor function:
this.removeClick = this.removeClick.bind(this);
The problem comes that if you click the bin when there is more than one item in the cart it does not delete the first element and it does not always delete the correct item, however, if there is only one item in the cart it will delete the correct one.
I also note that :
console.log("this is i" + e);
returns the correct value (the id of button in which it was clicked on)
I personally find it stressful to use splice. Why not use filter instead so you'll have something like this.state.clickedNum.filter(num => num.id !== e.target.id)
removeClick = (e) => {
id = e.target.id;
let clickedNum = this.state.clickedNum.filter(num => num.id !==id);
let isDisabled = this.state.filter(num => num.id !==id;
this.setState({
clickedNum: clickedNum,
isDisabled:isDisabled
})
}