React checkbox get (un)checked after the second click - javascript

Below is the part of the react component where a dynamic checkbox is listed:
<div className="pb-4">
<p>Choose the task owner(s):</p>
{
peerIds.map(id => {
if (currentUserId != id)
return (
<label className="checkbox-inline mr-3">
<input onChange={onChange_TaskOwner}
type="checkbox"
name="taskowner"
checked={st_taskOwnersId.filter(function (item) {return (item == id)}).length > 0}
value={peers[id].id} />
<span>{peers[id].fullName}</span>
</label>
)
})
}
<div style={{ clear: 'both' }}></div>
</div>
In the above code I set checked to false/true if the current id is already in the hook state object called st_taskOwnersId.
I store the Ids of the checked items using hook as below. onChange_TaskOwner function updates the st_taskOwnersId depending on whether it is checked or unchecked:
const [st_taskOwnersId, set_taskOwnersId] = useState([] as any);
const onChange_TaskOwner = (event) => {
event.preventDefault();
if (event.target.checked)
set_taskOwnersId([...st_taskOwnersId, event.target.value]);
else
set_taskOwnersId(st_taskOwnersId.filter(function (item) {
return (item != event.target.value);
}));
}
The code runs without errors. The only problem is I have to click twice to check/uncheck the check boxes. I have no clue why this is happening. Any help?

I believe the problem is on onChange_TaskOwner function. You should remove event.preventDefault(); call.
I tried to reproduce your component on this codepen, without the event.preventDefault(); works fine, if you add it, the same kind of error start happening.

Related

How to create dynamic search filter in react JS?

I'm trying to implement a search filter in reactjs, I've tried bind method to `onChange´ event but it triggered for every single letter. How can it make it work to after type 3 letters.
var FilteredList = React.createClass({
filterList: function(event){
var updatedList = this.state.initialItems;
updatedList = updatedList.filter(function(item){
return item.toLowerCase().search(
event.target.value.toLowerCase()) !== -1;
});
this.setState({items: updatedList});
},
getInitialState: function(){
return {
initialItems: [
"Apples",
"Broccoli",
"Chicken",
"Duck",
"Eggs",
"Fish",
"Granola",
"Hash Browns"
],
items: []
}
},
componentWillMount: function(){
this.setState({items: this.state.initialItems})
},
render: function(){
return (
<div className="filter-list">
<form>
<fieldset className="form-group">
<input type="text" className="form-control form-control-lg" placeholder="Search" onChange={this.filterList}/>
</fieldset>
</form>
<List items={this.state.items}/>
</div>
);
}
});
var List = React.createClass({
render: function(){
return (
<ul className="list-group">
{
this.props.items.map(function(item) {
return <li className="list-group-item" data-category={item} key={item}>{item}</li>
})
}
</ul>
)
}
});
React.render(<FilteredList/>, document.getElementById('app'));
https://codepen.io/mtclmn/pen/QyPVJp, i tried to change this code for my case but im stuck with triggering it after 3 letters scenario.
Any idea would be much appreciate.
onChange is triggered whenever the event happened. So when even.target.value as values input tag is changed then function is activated. Unlike Other methods like onClick or onSubmit that is activated after clicking or clicking the submit button, every change is detected by onChange event. That's how different between onChange and onClick. If you want to search after typing 3 letters then there is an easy one approach. Setting condition value.length >= 3 would be helpful.
https://codepen.io/jacobkim9881/pen/LYRjLwd
As the function which is pass to the onChange property of an input field is called any time you type on the keyboard. You can check inside of that function before performing any change which you need as updating the state If the value of event.target.value length is greather or equal to 3 like this
filterList: function(event){
if(event.target.value.length >= 3) {
// Here you can perform what you need
}
}
Modified your filterList function to only start the searching when there are 3 characters available, otherwise, return the initialItems list :
filterList: function(event){
if(event.target.value.length > 2){
var updatedList = this.state.initialItems;
updatedList = updatedList.filter(function(item){
return item.toLowerCase().search(
event.target.value.toLowerCase()) !== -1;
});
this.setState({items: updatedList});
}
else{
console.log(this.state.initialItems)
this.setState({items: this.state.initialItems})
}
}
Edit: the previous answer posted here, is doing the same as me. I have added an additional else condition to return the entire list when there is nothing on the search bar. Please change it to else if to suit your requirements.

getElementById after conditionally rendering with setState

I have a trigger button that will open a dialog asking if a user would like to enable text to speech. Once the dialog is open, I want to focus on the yes button within the dialog by getting the button element by its ID.
When the trigger is pressed, the following function is called:
private openTTSDialog = () => {
if (this.state.ttsDialog === true) {
this.setState({ ttsDialog: false })
} else {
this.setState({ ttsDialog: true }, () => {
// search document once setState is finished
const yesButton = document.getElementById('tts-dialog-yes-button')
log('yesButton', yesButton)
if (yesButton) {
yesButton.focus()
}
})
}
}
And my dialog is conditionally rendered with a ternary expression like this:
{
this.state.ttsDialog ? (
<div className="tts-dialog-container">
<div className="tts-dialog-text-container">
{session.ttsEnabled ? (
<div>
{
strings.disableTTS
}
</div>
) : (
<div>
{
strings.enableTTS
}
</div>
)}
</div>
<div className="tts-dialog-button-container">
<button
aria-label={strings.yes}
tabIndex={0}
className="tts-dialog-button"
id="tts-dialog-yes-button" // this is the button I want to focus
onClick={this.toggleTTS}
>
{
strings.yes
}
</button>
<button
aria-label={strings.no}
tabIndex={0}
className="tts-dialog-cancelButton"
onClick={this.closeTTSDialog}
>
{
strings.no
}
</button>
</div>
</div>
) : null
}
My log for yesButton is undefined. I thought adding the callback function to setState would fix this because I would be searching the document after setState was finished, but I'm still missing something. Any idea what it is?
In the constructor of your class, you should add a ref to your button:
this.myRef = React.createRef();
Then in your button :
<button
ref={this.myRef}
aria-label={strings.yes}
tabIndex={0}
className="tts-dialog-button"
id="tts-dialog-yes-button" // this is the button I want to focus
onClick={this.toggleTTS}
>
Finally, instead of doing:
const yesButton = document.getElementById('tts-dialog-yes-button')
You should do :
const yesButton = = this.myRef.current;
Actually I would also think this should work since you use a callback on setState, so the new render should have completed and the element should already be mounted and accessible. Anyway I think the idiomatic React way for this would be to use a ref (https://reactjs.org/docs/refs-and-the-dom.html) and put it on the button like <button ref={this.yesButton} ...>...</button> and then call this.yesButton.focus(). Have you tried that already?

How to stop an event from being further processed in React

I have a text input in my React app which I don't want to take inputs which are greater than 100. For example, If the entered value is 105, an alert is created and the event is terminated i.e changing input value is not gonna happen. Now I couldn't find a way to do this inside onChange function. Any help would be highly appreciated.
<input onChange={handleChange} name="t"/>
handleChange = e => {
if(e.target.value > 100){
alert("High")
//Here I want to stop event so that changing text in the input doesn't happen
}
}
Make it a controlled input and only set the value if a condition is met.
const App = () => {
const [value, setValue] = React.useState("");
const handler = (e) => {
const value = Number(e.target.value);
value <= 100 && setValue(value);
};
return (
<input onInput={handler} type="number" value={value} />
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If I'm understanding you properly, if a specific condition is not met, you want to prevent the input from reflecting the text the user just entered.
In order to accomplish this, you'll need to control your input's value via state.
That means doing something like this:
<input onChange={handleChange} name="t" value={this.state.tInput}/>
handleChange = e => {
if(parseInt(e.target.value) > 100) {
alert("High")
// set input state here to previous value to re-render and remove unwanted input values from input
return this.setState(({tInput}) => ({tInput}))
}
return this.setState({tInput: e.target.value})
}
handleChange = e => {
if(e.target.value > 100){
alert("High");
e.target.value = "";
}
else {
// your normal processing
}
}
should work.
Explanation:
Your code would simply not be executed in case the if condition is true.
The line e.target.value = "" doesn't actually "not show" the users input (as asked for in comment), but rather overrides it with empty string.
Mention:
This solution has nothing to do with React, but rather works in any context.

Checkbox becomes unclickable after checkbox limit function occur reactjs

I have a mapping function which shows JSON values into checkboxes, each checkbox triggers another 2 checkboxes, the JSON I am using have a min/max value which I made a function for to set min/max for checkbox selections in each section. My problem is that once the parent & child checkboxes are clicked and then I redo the process where I click it to shrink it and click it again to expand it, the children checkboxes stops being clickable.
The checkbox values are passed as props from Checkbox.js to Itemlist.js where the fetch/map happens.
React Live Snippet: https://codesandbox.io/embed/2178pwz6or?fontsize=14
Checkbox.js
class Checkboxes extends React.Component {
constructor(props) {
super(props);
this.state = {
currentData: 0,
limit: 2,
checked: false
};
}
selectData(id, event) {
let isSelected = event.currentTarget.checked;
if (isSelected) {
if (this.state.currentData < this.props.max) {
this.setState({ currentData: this.state.currentData + 1 });
} else {
event.preventDefault();
event.currentTarget.checked = false;
}
} else {
if (this.state.currentData > this.props.min) {
this.setState({ currentData: this.state.currentData - 1 });
} else {
event.preventDefault();
event.currentTarget.checked = true;
}
}
}
render() {
const input2Checkboxes =
this.props.options &&
this.props.options.map(item => {
return (
<div className="inputGroup2">
{" "}
<div className="inputGroup">
<input
id={this.props.childk + (item.name || item.description)}
name="checkbox"
type="checkbox"
onChange={this.selectData.bind(
this,
this.props.childk + (item.name || item.description)
)}
/>
<label
htmlFor={this.props.childk + (item.name || item.description)}
>
{item.name || item.description}{" "}
{/** <img src={this.props.img} alt="" /> <span className="pricemod">{props.childprice} SAR</span>
*/}
</label>
</div>
</div>
);
});
return (
<form className="form">
<div>
{/** <h2>{this.props.title}</h2>*/}
<div className="inputGroup">
<input
id={this.props.childk + this.props.name}
name="checkbox"
type="checkbox"
checked={this.state.checked}
onChange={this.selectData.bind(
this,
this.props.childk + this.props.uniq
)}
onChange={() => {
this.setState({ checked: !this.state.checked });
}}
/>
<label htmlFor={this.props.childk + this.props.name}>
{this.props.name}{" "}
</label>
</div>{" "}
{this.state.checked ? input2Checkboxes : undefined}
</div>
</form>
);
}
}
export default Checkboxes;
In your code sandbox snippet, just change line 24 in Checkbox.js component to this
if (this.state.currentData >= this.props.min) {
To help you see why you're encountering the issue, you can modify the selectData method of Checkbox.js with some helpful debugging statements:
// Checkbox.js
selectData(id, event) {
let isSelected = event.currentTarget.checked;
console.log(
`Attempting to ${isSelected ? "check" : "uncheck"} ${
event.currentTarget.id
}`
);
console.log(`min is ${this.props.min}, max is ${this.props.max}`);
console.log(`currentData is: ${this.state.currentData}`);
if (isSelected) {
if (this.state.currentData < this.props.max) {
console.log('Allowed to check. Incrementing currentData')
this.setState({ currentData: this.state.currentData + 1 });
} else {
console.log('Not allowed to check: greater than or equal to max')
event.preventDefault();
event.currentTarget.checked = false;
}
} else {
if (this.state.currentData > this.props.min) {
console.log('Allowed to uncheck. Decrementing currentData')
this.setState({ currentData: this.state.currentData - 1 });
} else {
console.log('Not allowed to uncheck. Less than or equal to min')
// event.preventDefault();
// event.currentTarget.checked = true;
}
}
}
You'll notice, as you check "Side 1" and then un-check it, that you do not satisfy the condition if (this.state.currentData > this.props.min), and so you never get to actually decrement this.state.currentData.
To fix this, you need to do one of 2 things:
Change your conditional to be if (this.state.currentData >= this.props.min)
OR
Change your data.json to set your min to 0 rather than 1.
UPDATE
You also have an issue when you do the following steps:
Check a side collection (reveals sides)
Check a side (increments currentData)
Uncheck the side collection (hides sides)
Check the side collection again
At this point, your sides are revealed, but they are unchecked
AND your currentData is already 1.
You cannot check any sides because of this.
To fix this, you can either:
Reset currentData to 0 whenever the side collection is checked/un-checked.
OR
Change your input2Checkboxes method to take into account which sides have been checked and keep their states, even if the side collection gets unchecked.
The first of the above 2 options is simpler to do. Within the render method of Checkbox.js, you need your side collection input's onChange to look like this:
// Checkbox.js
onChange={() => {
this.setState({
checked: !this.state.checked,
currentData: 0
})
}}
I've updated the forked code sandbox to show the working demo now:

how can i handle event capturing in react component?

Please read code first.
After css processing, it seems like memo application's single memo paper.
The goal of the component is to print a 1 when clicked(in real, the goal is to hadding redux store's state).
When i click outside of div component, it works very well. ( it printed '1' )
but when i clicked inner div component(title, date,content), onClick event also proceed ( it printed '')
how can i prevent non-valued print?
My code :
class container extends Component {
handleState = (event) => {
console.log(event.target.id)
}
render(){
return(
<div onClick={handleState} id={value}>
<div>title</div>
<div>date</div>
<div>content</div>
</div>
)
}
}
container.defaultprops = {
value: 1
}
thanks.
You can use currentTarget:
handleState = (event) => {
console.log(event.currentTarget.id)
}
About difference between target and currentTarget:
https://stackoverflow.com/a/10086501/5709697
You can use currentTarget to check if it's the target since you bound the handler to the parent e.g.
handleState = (event) = > {
if (event.target == event.currentTarget) {
console.log(event.target.id)
}
}

Categories