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:
Related
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.
Basically, the code snippet below is for the Close/Edit icon: Once clicked in "Close" mode, it will change to the "Edit" icon and pass the rowId and a "1" parameter in the handleEdit function; and, once in "Edit" mode, it will pass the rowId and a "0" parameter.
The problem is that it only goes to the condition of editClose === 1, although it updates the editDeleteTag to 1; it never seems to render the stylesEditOptions icon along with it's condition.
I'm new to React, so I might be missing something here.
childComponenent.jsx
funcEdit = (editClose) => {
if (editClose === 0) {
return (<div className={styles.editOptions}>
<Input type="button" className={styles.closeIcon} onClick={() => this.props.handleEdit(rowIndex, 1)} />
</div >)
} else {
return (<div className={styles.editOptions}>
<Input type="button" className={styles.EditIcon} onClick={() => this.props.handleEdit(rowIndex, 0)} />
</div >)
}
}
render()
let locArr = [...this.state.mainArray];
For looop .... {
if (locArr[i].editOrDeleteTag === 0) {
locArr[i].editOrDelete = this.funcEdit(1);
} else {
locArr[i].editOrDelete = this.funcEdit(0);
}
}
return(
...
<BootstrapTable data={locArr}
...
)
parentComponent.jsx
handleEdit = (rowId, toggle) => {
let locArr = [...this.state.mainArray];
locArr[rowId.rowIndex].editOrDeleteTag = toggle
this.setState({ mainArray : locArr });
};
The most likely reason is the fact that you are mutating the state.
When making state changes to a nested object you need to update all parent elements.
So in your handleEdit try to use
locArr[rowId.rowIndex] = {
...locArr[rowId.rowIndex],
editOrDeleteTag: toggle
};
instead of
locArr[rowId.rowIndex].editOrDeleuteTag = toggle;
When I click the + button to expand the group and click the checkbox of group 1, it executes as expected.
But the problem is,
If I click the checkbox of group 1 first and then expand the group by clicking + button,
It shows all user clicked which is correct, if I clicked the checkbox of group 1 again, the checkbox of group 1 become unchecked, but the checkboxes of users do not get unchecked.
Reproduce step:
refresh the page > Click group 1 checkbox > click + to expand > click group 1 checkbox again > then you'll see user checkbox do not become unselected
SandBox Link below:
https://codesandbox.io/s/dazzling-antonelli-gl9rm
after Unselecting group 1, 2 checkboxes of users do not become unselected:
Your isExist method returns undefined instead of false, fix it to return always either true/false.
isExist = (id, group) => {
if (!this.props.selected) {
console.log("selected = null");
return false;
}
return (
this.props.selected.find((ele) => ele.id === group + id) !== undefined
);
};
This way you don't have to manually convert its return value to boolean using !!.
I suggest to also refactor your handleSelected method to
handleSelected = async (e) => {
const { selected } = this.state;
if (e.target.checked) {
let temp = { id: e.target.id, name: e.target.name };
return this.setState({ selected: [...selected, temp] });
}
this.setState({
selected: selected.filter(({ id }) => id !== e.target.id)
});
};
The issue you are experiencing is a result of a problem in the first render - as you can see - you have "undefined Contact". You need to solve this issue, and then the grouping will work as you desire.
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.
Following is my handleScroll function in which I am trying to add red class if it scroll down to a certain limit else apply blue. However this is only going inside the else statement and also console.log(e.target.scrollTop); its consoling as undefined. Let me know what I am doing wrong here.
Code -
handleScroll(e) {
console.log(e.target.scrollTop);
if (window.screenX > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Codesandbox - https://codesandbox.io/s/silly-feynman-m6hp1
I would highly recommend adding an extra check to your condition. When you scroll a single-time, you update your component-state multiple times after a certain range (100), which is unnecessary. You only need to update it once.
It will keep updating because you meet the condition inside handleScroll, even though the background has already changed. The sheer amount of updates can cause your app to crash.
Try something like this it will update your component-state as expected and only when necessary: https://codesandbox.io/s/white-architecture-jepyc
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
navBckgroundColor: `blue`,
altered: false
};
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
//use arrow function instead so that its "this" keyword points to the component's execution context. Otherwise, "this" will point to the global window object where you cannot use this.setState.
handleScroll = e => {
if (window.pageYOffset > 100 && !this.state.altered) {
this.setState({
navBckgroundColor: `red`,
altered: true
});
} else if(window.pageYOffset < 100 && this.state.altered) {
this.setState({
navBckgroundColor: `blue`,
altered: false
});
}
};
render() {
return (
<div className="App">
<Navbar bckGroundColor={this.state.navBckgroundColor} />
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
Use window.scrollY instead of window.screenY and also bind the handleScroll method.
handleScroll = (e) => {
if (window.scrollY > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Working demo
Please use
handleScroll = e => {
console.log(e.target.scrollTop);
if (window.scrollY > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Please see the workable code on :
https://codesandbox.io/s/friendly-swirles-bwl06
also your window.screenX will always output the same value, and thus no change to the colors.
I have changed that in the code as well