Increment and decrement counter on checkbox - javascript

I am working with Next.js to handle a question form that has different kinds of questions with single choice answers and multiple choice. I have a progress bar in which its percentages change according to selected answers. when I select a question's answer I increment a counter and when I unselect an answer decrement the counter.
1 - When I select a checkbox of a question at first increment the counter, but it must not increment when I select the second checkbox in the same question.
2 - When I unselect multiple-choice questions answer it must not decrement if any one of them is selected it must not decrement until the last one is unselected.
Now when selecting the first time it increments the counter but after that, if I select or unselect, it decrements.
Please help me to handle this.
Question sample:
This is my code:
<MuilteSelectQuestion
key={item.attributes.Number}
id={item.attributes.Number}
data={item}
name={`${item.attributes.Number}`}
handleMultiChecked={handleMultiChecked}
/>
The component:
<div className="relative flex items-start">
{Object.keys(attributes?.options).map(item => (
<>
<div className="flex items-center h-5">
<input
{...feild}
{...props}
id={item}
type="checkbox"
name={`${name}[]`}
value={`${item}`}
onClick={e =>
handleMultiChecked(
`question${id}`,
e.target.checked
)
}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div>
<div className="ml-2 mr-3 text-sm">
<label
htmlFor={item}
className="font-medium text-gray-700"
>
{item}
</label>
</div>
</>
))}
</div>
The function:
function handleMultiChecked(val, checked) {
setCompareTitle(val);
if (val !== compareTitle && checked) {
setCounter(++counter);
} else {
setCounter(--counter);
}
}
useEffect(() => {
setPercent((parseInt(counter) / parseInt(dataCount)) * 100);
}, [counter]);
The data that come from backend:

I'd suggest you have another state to track all questions' counters
const [questionCounters, setQuestionCounters] = useState({})
Apply it to handleMultiChecked
function handleMultiChecked(val, checked) {
setCompareTitle(val);
const currentQuestionCounter = questionCounters[val] || 0 // if this question is not in the state, the default counter will be 0
const updatedQuestionCounter = checked ? currentQuestionCounter + 1 : currentQuestionCounter - 1 //update the current question's counter based on checked
//no ticks on the current question, we decrease the main counter
if (currentQuestionCounter === 1 && updatedQuestionCounter === 0) {
setCounter(--counter);
}
//first tick, we increase the main counter
if(currentQuestionCounter === 0 && updatedQuestionCounter === 1) {
setCounter(++counter);
}
//apply the latest counters' changes for question list
setQuestionCounters({
...questionCounters,
[val]: updatedQuestionCounter
})
}
useEffect(() => {
setPercent((parseInt(counter) / parseInt(dataCount)) * 100);
}, [counter]);

Related

React: Managing dozens of button states in a function component

This is my first React project. I am using Bootstrap grid to make a "+" shaped cluster of buttons that go from 1 to 31, which means I cannot use map to reduce the amount of lines of code I have.
const ButtonPlusGrid = () => {
const [colorButtonID, setColorButtonID] = useState(0);
const handleColorChange = () => {
if (colorButtonID === 3) {
setColorButton1(1);
} else {
setColorButton1(colorButtonID + 1);
}
};
let buttonClass;
if (colorButtonID === 0) {
buttonClass = 'btn-secondary col-2';
// btn-secondary for gray color.
} else if (colorButtonID === 1) {
buttonClass = 'btn-success col-2';
// btn-succes for green.
} else if (colorButtonID === 2) {
buttonClass = 'btn-danger col-2';
// btn-danger for red.
} else if (colorButtonID === 3) {
buttonClass = 'btn-primary col-2';
// btn-primary for blue.
}
// the empty divs are to correct the spacing for each row.
return (
<>
<div className="row">
<div className="col-1" />
<div className="col-1" />
<div className="col-1" />
<button
type="button"
className={buttonClass}
id="num1"
onClick={handleColorChange}
>
1
</button>
<div className="col-1" />
<div className="col-1" />
<div className="col-1" />
</div>
<div className="row">
<div className="col-1" />
<div className="col-1" />
<div className="col-1" />
<button
type="button"
className="btn-secondary col-1"
id="num2"
>
2
</button>
<button
type="button"
className="btn-secondary col-1"
id="num3"
>
3
</button>
<div className="col-1" />
<div className="col-1" />
<div className="col-1" />
</div>
...
</>
);
};
I am trying to change the color of each button on click (by changing their class names) but doing it this way will be difficult, replicating the same logic 31 times is too much code, and I can't think of any elegant way to handle it.
I created the button grid using Bootstrap grid to make it follow a specific shape.
For each button, I assigned a specific ID (num1 => num31) because I am planning to save each state in a database and when I open the webpage again, each button keeps the same colors.
These are just 2 rows from the whole grid. I just wrote the code to change the color of one button based on the state.
My question is:
How do I handle all those 31 buttons? Is there a more efficient way to manage each state? or should I overhaul this code because it's repetitive?
I thought of creating a React class component but I don't have an idea how it can be designed for each button, but I'll let you guys decide what's the best approach here.
PS: here is how the buttons look like: Here is the link.
You can manage the state for all the buttons in 1 single state variable. I've created an example to demonstate
import React from "react";
// Just an example to demonstate the functionality, you can changes this to return the correct css class instead.
const getStyle = (number) => {
switch (number) {
case 3:
return "pink";
case 2:
return "green";
case 1:
return "purple";
default:
return "papaywhip";
}
};
export default function App() {
const [colorIdMapping, setColorIdMapping] = React.useState({});
const handleClick = (index) => {
setColorIdMapping({
...colorIdMapping, // Remember all the other values
[index]: ((colorIdMapping[index] || 0) + 1) % 4 // Set current index to number 0, 1, 2 or 3
});
};
return (
<div className="App">
{/* Demo of rendering 10 buttons, render them any way you like. */}
{Array.apply(0, Array(10)).map(function (x, i) {
return (
<div
style={{ background: getStyle(colorIdMapping[i]) }}
onClick={() => handleClick(i)}
>
button
</div>
);
})}
</div>
);
}
You could also create a button component that keeps track of its own state. But in this case I would manage all the state in one place like the example above, so you can easily store and retrieve it (from the db).
But as an example, it could look like this:
const MyButton = () => {
const [number, setNumber] = React.useState(0);
const handleClick = () => {
setNumber((number + 1) % 4);
};
return (
<div onClick={handleClick} style={{ background: getStyle(number) }}>
button
</div>
);
};
Working example: https://codesandbox.io/s/loving-feistel-jw8vci

Increment and decrement counter according to select or unselect tag

I am working with the next js to handle a question form, I have multiple questions that have multiple answers, and the user may select one or multiple tags for a question.
if a user selects one or more tag to answer a question it must increment the counter just one time, If a user unselect a tag it must not decrement until unselect the last tag of the related question.
1 - When the user selects a tag of a question at first increment the counter, but it must not increment when I select the second tag in the same question.
2 - When the user unselects tags of question it must not decrement if any one of them is selected it must not decrement until the last one is unselected.
Now when selecting the first tag it increments the counter but after that, if I select or unselect, it does not decrement or increment.
Please help me to handle this.
My code:
<TageQuestion
key={item.attributes.Number}
id={item.attributes.Number}
data={item}
name={`${item.attributes.Number}`}
errors={errors}
touched={touched}
handleSelectOption={handleSelectOption}
/>
The component:
<div className="mt-3">
{Object.keys(attributes.options).map(item => (
<div className="inline" key={item}>
<input
{...feild}
{...props}
className="hidden"
id={item}
value={item}
type="checkbox"
name={`${name}[answer]`}
/>
<label
htmlFor={item}
id={item + "lable"}
onClick={e => {
selectTage(e.target);
handleSelectOption(`question${id}`, item);
}}
className="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-gray-100 text-gray-800 mr-3 mb-2 cursor-pointer"
>
{item}
</label>
</div>
))}
</div>
The function:
function handleSelectOption(title, item) {
setCompareTagTitle(title);
if (title !== compareTagTitle) {
setTagItem([...tagItem, item]);
setCounter(++counter);
} else if (title === compareTagTitle) {
setCounter(counter);
} else {
setCounter(--counter);
}
}
Data that come from the backend:
we should consider 2 steps first in child component TagQuestions then in parent component [type].js
step1:
//TagQuestions.js
const [intialRender, setIntialRender] = useState(true);
// if component render for first time
const selectTag = (e) => {
// we should change intialRender to false to say that user select somthing
....
if (tage.length > 0) {
const isExit = tage.filter((item) => item.name === el.innerHTML);
if (isExit.length === 0) {
setTage([...tage, { name: el.innerHTML }]);
if (intialRender) {
setIntialRender(false);
}
}
} else {
setTage([...tage, { name: el.innerHTML }]);
if (intialRender) {
setIntialRender(false);
}
}
....
}
//use effect function to if user unselect all tags after selctions
useEffect(() => {
if (tage.length === 0 && !intialRender) {
//call parent function and set empty param to true.
handleSelectOption(`question${id}`, "", true);
}
}, [tage]);
step2(parent component):
const [compareTagTitle, setCompareTagTitle] = useState([]);
function handleSelectOption(title, item, empty = false) {
if (empty) {
const filter = compareTagTitle.filter(
(value) => value.toString() !== title.toString()
)
setCompareTagTitle(filter);
setCounter(--counter);
} else {
if (!compareTagTitle.includes(title)) {
setCompareTagTitle([...compareTagTitle, title]);
setCounter(++counter);
}
}
}

How to get the value of a radio input which is by default checked?

I have a radio button inside a .map method and the first is checked defaultChecked={index === 0}, how do I get the value of the option here? without using plain javascript
const [optionData, setOptionData] = useState({})

{variant.options.map((option, index) => {


<input
type="radio"
name="variant-select"
id="variant-select"
className="focus:ring-0 focus:ring-offset-0 focus:text-red-600 text-red-600 border-none -mt-1 cursor-pointer z-10"
defaultChecked={index === 0}
value={index}
onClick={(e) => {
setOptionData(option)
 }
}}
/>
}
}
This syntax may help you:function myFunction() { var x = document.getElementById("myRadio").value; document.getElementById("demo").innerHTML = x; }

react hooks: input onChange first character can not be deleted

I got some inputs to create/update a formular,
1) if value={question}, everything works except that I can not delete the last character (= first character of the input)
2) if I dont mention value, it's all good except when I want to change questions orders with Chevron icon button, database is well changed but input value is still displayed at the last place.
<input
type="text"
value={question}
placeholder="blabla"
onChange={event => {
event.preventDefault();
const value = event.target.value;
setUpdatedQuestion(value);
}}
/>
I tried to add if (event.target.value == "" || event.target.value) to onChange but does not work either
OK I found something, but it is a McGyver tip, not very clean : adding one space before all new add question ahah. But then I can't see my placeholder anymore :/
FYI : question is coming from a questions.map, setUpdatedQuestion do update questions, newOrder also do update questions
//in QuestionsContent.js (questions is a questions tab)
const QuestionsContent = props => {
return (
<form onSubmit={onSubmit}>
{isLoading === false && questions.length > 0
? questions.map((question, i) => {
return (
<div key={i}>
<QuestionLine
{...question}
questions={questions}
setQuestions={setQuestions}
setNewOrder={setNewOrder}
/>
</div>
);
})
: null}
<button
onClick={event => {
event.preventDefault();
setAddQuestion({
question: null,
type: "texte"
});
}}
>
Add a question
</button>
</form>
);
};
//in QuestionLine.js:
const QuestionLine = ({
question,
setNewOrder,
setQuestions,
questions
}) => {
const [updatedQuestion, setUpdatedQuestion] = useState("");
// * UPDATE QUESTION *******************************
useEffect(() => {
if (updatedQuestion !== "") {
const tab = [];
for (let j = 0; j < questions.length; j++) {
if (j === i) {
const newObject = {
question: updatedQuestion,
type: type
};
console.log("adding updatedQuestion ===>", newObject);
tab.push(newObject);
} else {
tab.push(questions[j]);
}
}
setQuestions(tab);
setUpdatedQuestion("");
}
}, [updatedQuestion]);
return (
<div >
{/* QUESTION */}
<input
type="text"
value={question}
placeholder="blabla"
onChange={event => {
event.preventDefault();
const value = event.target.value;
setUpdatedQuestion(value);
}}
/>
</div>
);
};
thanks for your precious help
The problem probably comes from the condition in the useEffect : when you want to delete the last character of the string, the state of updatedQuestion is empty and therefore the condition is not executed

Redux-Form old/incorrect field values when triggering field swap inside FieldArray

I am using Redux-Form 6.8.0 for my project. I am creating traveling application that calculates distance and duration of trip. Trip can have one or many stops. Stops can be picked from Google Place AutoComplete
and after picking desired place from google places application calculates distance with Google Direction Service .
I managed to perform all calculations when user selects place from dropdown but when user swap stops/fields (used fields.swap(indexA:Integer, indexB:Integer)) incorrect results are displayed. Inside calculation method I "debugged" values and when user perform fields swapping, values (indexes) doesn't change at all but field array changes are rendered correctly.
Also, I am adding new elements in redux-form store because I need some values submitted even if they doesn't have fields at all. Not sure if that's good approach so I am open for suggestions.
Does someone knows where is the problem?
Here is my code:
Price calculation:
updateStopDistanceAndTime = () => {
let self = this;
if (this.props.fields.length >= 2) {
let waypoints = [];
this.props.fields.getAll().map(function (value, index) {
console.log(value);
if (value.place) {
waypoints.push({
place: value.place,
placeId: value.placeId
});
}
});
calculateTimeAndValueBetweenStops(waypoints).then(function (result) {
if (Object.keys(result).length > 0) {
let currentValues;
result.map(function (value, index) {
currentValues = self.props.fields.get(index);
if (value.end_address !== value.start_address) {
currentValues.distance = value.distance;
currentValues.duration = value.duration;
// self.props.changeFieldValue('busRentForm', index + ".distance", value.distance);
// self.props.changeFieldValue('busRentForm', index + ".duration", value.duration);
}
else {
// self.props.changeFieldValue('busRentForm', index + ".distance", null);
// self.props.changeFieldValue('busRentForm', index + ".duration", null);
currentValues.distance = null;
currentValues.duration = null;
}
self.props.fields.remove(index);
self.props.fields.insert(index, currentValues);
});
}
});
}
}
Field Array:
render() {
const {
stop,
index,
dayIndex,
fields,
isDragging,
connectDragSource,
connectDropTarget,
} = this.props
return connectDragSource(connectDropTarget(
<div key={index} className="">
<div>
<div className="col-md-6 col-xs-7">
<Field name={`${stop}.place`}
type="text"
component={renderStopInput}
icon={String.fromCharCode(97 + index)} // (97) is A in ascii table
label={(index === 0) ? "Starting location" : (index === fields.length - 1) ? "Destination location" : "Stop location"}
placeholder=""
index={index}
/>
<span className={index !== fields.length - 1 ? "vertical-dash" : null}></span>
{(index !== 0 && index !== fields.length - 1) ?
<button className="btn btn-fab btn-rearrange" type="button" onClick={() => {
fields.swap(index, index + 1);
}}><i className="fa fa-exchange"></i></button>
:
null}
</div>
<div className="col-md-1 stack">
{(index !== fields.length - 1 && index !== 0) ?
<button className="btn-remove" type="button" onClick={() => fields.remove(index)}>
<span className="icon-Cancel"></span>
</button>
: null}
</div>
{(index !== fields.length - 1 && fields.get(index)) ?
<StopInfo
departureTime={fields.get(index).time }
distance={fields.get(index).distance}
duration={fields.get(index).duration}
/>
: null}
</div>
</div>
));
}

Categories

Resources