I in my React Application, I have a set of mapped questions from backend data where the user selects a response with a radio button:
<RadioGroup row>
{data.response.map((dt, rIndex) => {
return (
<div className="answerContainer">
<FormControlLabel
className="MuiTypography-body1"
value={dt.value}
onChange={() => {
debugger;
setAnswer(
dt.value,
data.questionTitle,
qIndex,
rIndex
);
}}
checked={selectedAnswers[qIndex] === rIndex}
control={
<Radio
className="PrivateRadioButtonIcon-root-9"
required={true}
/>
}
label={dt.value}
/>
</div>
);
})}
</RadioGroup>
I want to disable the "next" navigation button unless all answers are checked off. I created a state called proceed which by default is false:
const [proceed, setProceed] = React.useState(false);
In my handleChange event if the number of questions is less than the number of answers, the button is disabled.
const setAnswers = async () => {
if (questionsData.length < selectedAnswers.length) {
setProceed(false);
return;
}
Then I added this statement in my setAnswers handleChange function to check if the user can proceed to the next page in the stepper:
if (questionsData.length === selectedAnswers.length) {
setProceed(true);
}
Finally I pass the handleChange function into my button:
<button
className={proceed ? 'enabled' : 'disabled'}
onClick={() => {
setAnswers();
}}
> Next </Button>
The setProceed === false condition works correctly. set proceed === true appeared to be working, but I discovered if I clicked the last answer in the questions without clicking the others, setProceed is === true is triggered and the button is enabled, allowing users to skip the questions.
I added the required flag to the MUI radio button, but the function bypasses it.
Similar to this answer on S/O (which is related to PHP), How can I ensure that all answers must be selected before enabling this state change in React?
Instead of using the length to compare if the all the values are filled . you can use the every from Array.
If your selected Answers contains a list of boolean ( [ true, false ] ) values then you can do
const isAllAnswersSelected = selectedAnswers.every(Boolean)
isAllAnswersSelected will be true if all the items in the selectedAnswers is true else it will return false.
Refer
Array Every
Material UI doesn't provide validations and you must take care of validations and error state yourself.
required form attribute adds the asterisks to the label and not the validations.
<Radio className="PrivateRadioButtonIcon-root-9" required={true} error={questionHasError(qIndex)}/> will again add style level error indicators.
You can maintain a state of answered questions instead?
const [answeredQuestions, setAnsweredQuestions] = useState([]);
.
.
.
const setAnswers = async (qIndex) => {
const updatedAnsweredIdx = [...answeredQuestions, qIndex];
setAnsweredQuestions(updatedAnsweredIdx );
// check if all the questions have been selected/answered
if(allQuestionIndex.every(idx => updatedAnsweredIdx.includes(idx))){
setProceed(true);
} else {
setProceed(false);
}
}
You can then enhance validations by tracking if question is answered and pass error prop to highlight error state.
Related
Currently I'm working on Alarm clock app and I want to do it in way so you can add multiple Alarms. Every Alarm is stored in alarms:[] state in App.js file. If alarm is added,I want to display that alarm as a li element under the clock and I want to be able to remove it by clicking on X icon. Also ,when current time === time set for alarm ,Ring.js component renders and alarm starts ringing. When alarm is ringing there is 'turn off' button. How is it possible to delete this specific alarm which is ringing ,from state array after click on turn off button?
I've tried to send removeAlarm function and alarm(which may help in determining which alarm from array delete)as a prop to this component when condition if fulfilled.
function checkTime(){
if(time.alarms[0]){
const alarms = time.alarms.map(function(alarm,i){
if(time.currentHour === alarm.hour && time.currentMinute === alarm.minute && time.currentSecond
>= 0){
return <Ring message={alarm.message} key={i} alarm={alarm} removeAlarm={removeAlarm} />
}
})
return alarms;
}
}
removeAlarm function:
function removeAlarm(alarm){
setTime(prevState => ({
...prevState,
alarms:[...prevState.alarms.filter(el => el !== alarm)]
}))
}
Ring.js file
let message = props.message;
function removeAlarm(alarm){
props.removeAlarm(alarm);
}
function turnOff(e,alarm){
e.preventDefault();
setShowRing(false);
removeAlarm(alarm);
}
<form>
<h3>{message}</h3>
<button onClick={turnOff}>TURN OFF</button>
</form>
I can't figure it out how to do that. I don't know how to use that passed function or how to determine in that onClick function that THIS is that button which has to be send to removeAlarm function ,map thru state and remove that specific one.
Also second problem which I've noticed is with React Spring Transitions. I'm using it in Ring.js,Alarm.js and want to use it also for listing active alarms in ListAlarms.js. I'm using it the exact same way as in first two components but for ListAlarms.js it's not working and I don't undestand why. My goal is to display those active alarms with transitions not just 'blink' there.
Thank you.
CodeSandBox link here
OK some corrections but you have to alter the transitions
First of all you need to filter your list by id, in order to remove correctly the alarm.
function removeAlarm(alarm){
setTime(prevState => ({
...prevState,
alarms:[...prevState.alarms.filter(el => el.id !== alarm.id)]
}))
}
Secondly, I have removed the from property from your transition, since every new object was positioned on top of others. Also, instead of null for the key I used mapping to each item's id.
(item) => item.id
Finally I corrected the order in map function
{listAlarmTransitions.map(({ item, props, key }) => (
So it became
const listAlarmTransitions = useTransition(props.alarms, (item) => item.id, {
enter: { opacity: 1 },
leave: { opacity: 0 }
});
return (
<ul>
{listAlarmTransitions.map(({ item, props, key }) => (
<animated.div key={key} style={props}>
<li
key={item.id}
id={item.id}
onClick={() => {
removeAlarm(item);
}}
>
<FontAwesomeIcon icon={faTimesCircle} className="listIcon" />
<h3>{item.message}</h3>
<span>
{item.hour}:{item.minute}
</span>
</li>
</animated.div>
))}
</ul>
);
Check this sandbox
https://codesandbox.io/s/broken-morning-upqwp
You are filtering out objects/references you should filter out by id.
Your passed alarm argument is an object and your alarms filter array contains objects, find a unique property which you can filter against, by looking at your code, it should be id.
Something like this:
function removeAlarm(alarm){
setTime(prevState => ({
...prevState,
alarms:[...prevState.alarms.filter(el => el.id !== alarm.id)]
}))
}
i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.
I have an array that creates a mapping of items with checkboxes. each item has a checked state:
const [checked, setChcked] = React.useState(false)
So the user checks say 5 out of the 20 checkboxes and then press a button (the button is in the higher component, where there is a mapping that creates this items with checkboxes) and it works as intended. But, after the button is pressed and the modal is closing, after I open the modal again, these 5 checkboxes are still checked. I want them to restart to be unchecked just like when I refresh and the state vanishes. Now, I am aware of techniques such as not saving state per each item and just saving the state of the array of items in the higher component but I am confused as I have heard that hooks were created so that it is good practice to sometime save state in dumb components.
Is there a simpler function to just restart to initial value?
Edit:
adding the code
<div>
{policyVersionItems.map(item=> (
<PolicyVersionItem
key={pv.version}
policyVersionNumber={item.version}
policyVersionId={item._id}
handleCheck={handleCheck}
>
{' '}
</PolicyVersionItem>
))}
</div>
And the item
const PolicyVersionItem: React.FunctionComponent<PolicyVersionItemProps> = props => {
const { , policyVersionNumber, policyVersionId, handleCheck } = props
const [checked, setChcked] = React.useState(false)
return (
<Wrapper>
<Label dark={isEnabled}> Version {policyVersionNumber}</Label>
<Checkbox
checked={checked}
onClick={() => {
if (isEnabled || checked) {
setChcked(!checked)
handleCheck(policyVersionId, !checked)
}
}}
/>
</Wrapper>
)
}
Some of it is not relevant. the handle check function is a function that returns data to the higher component from the lower component for example.
I'm stuck in a sutiation where I need to disable the check boxs except the one which is checked.
The checkbox is dynamically created with the help of API and key will be passed to each checkbox. Is there a way to achieve above requirement.
Thanks
{this.state.imageinfo.map((image, key) => {
return(
<div key={key}>
<input type="checkbox"
onClick={(e)=> this.setCoverImage(e,key)}>Cover Image
<div className="delete-image" onClick={() => this.removeImage(key)}>
×
</div>
</div>
);
})
}
You could have that checkbox change a boolean saved in state and have the disabled attribute of all the others equal to that sate object.
use radio instead of checkbox
checkboxes behaviour is multiple selection, while radio isnt
if you want to use checkboxes:
setCoverImage(e, key) {
...
this.setState({active: key})
}
render() {
...
<input type="checkbox"
onClick={(e)=> this.setCoverImage(e,key)} checked={this.state.active === key}>
You are required to set a checked keyword which is boolean in an array which is set in your state. When ever function is called then it will set checked keyword in only that element of Array where the key is provided. Rest of The checked keyword in Arrays will be deleted.and then you will set the Updated Array in your State. Like this.
setCoverImage=(key)=>{
const ItemKeyTobeChecked=key;
const imageinfoArray=this.state.imageinfo;
for(var i=0;i<imageinfoArray.length;i++){
if(key===i){
imageinforArray[key].checked=true;
}
else{
if(imageinforArray[i].checked){
delete imageinforArray[i].checked
}
}
}
this.setState({
imageinfo:imageinfoArray
})
}
I am wanting to dynamically create input field values for each category a user creates, the issue is how can I keep track of what the user enters into the input field. As I cannot create X amount of states as it is dynamic. Any tips would be much appreciated, my code is shown below:
var categories = newData.map((category,index) => {
console.log(category)
return (
<div className="content row marginCenter" key={category._id}>
<p>{category.category}</p>
<input type="text" /> //How do I keep track of what was entered for this input field??
<button onClick={() => this.addCategoryLink(category._id)}>Add
link</button>
</div>
)
})
I am wondering how to bind that to the button element
The React docs have a section related to the core of this question:
https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers
Assuming your state holds an array of "categories" objects- essentially, I think what you're looking for boils down to something like this in your map function:
{this.state.categories.map(category => (
<input
type="text"
onChange={event => this.handleCategoryChange(category, event)}
value={category.value}
/>
)}
And then a change handler that looks something like this:
handleCategoryChange = (category, event) => {
const value = event.currentTarget.value;
this.setState(state => {
// Create a copy of the categories array:
const categories = [...state.categories];
// Create a copy of the category, with an updated value:
categories[category.index] = {
...category,
value
};
// Update state with the new values:
return { categories };
});
};
Here's a simple demo:
https://codesandbox.io/s/woqpwvl777
i have other Way for doing this , Of course this way just working well in some situation , forExample when you have just 1 or 3 value
i think you wanna create Input , and there Input are dynamic , and you want define that , if user click in first Button , you get and use first TextInput (value)
in my way ( again i say this : this way just well in some situation ) , we Create data Json like this
[
{ id: n ,
category: 'some',
value: ''
}
in this structure Value key , in the mounting contain nothing or null value if the Value not defined before
for now i create one handler method and this method, called after onChange Event fired on
<input onChange={(e) => this.getValue(category.id,e)} />
that element , this means when user start fill input onChange event handle function and update your state
getValue(id,e) {
let thisId = id-1;
let vs = this.state.c;
vs[thisId].value = e.target.value;
this.setState({
c:vs
});
let v = this.state.c[thisId];
console.log(v);
}
i create Pen in this address -> https://codepen.io/hamidrezanikoonia/pen/vRyJRx?editors=1111
you can check console , for more details ( open console tab in codepen )
and for more details , i create two method , the first fired when input (text) filled ( onChange event ) , and the other fired when clicked on button ( click event )