I created a simple logic: when you click on a certain block, the classname changes, but the problem is that when you click on a certain block, the classname changes and the rest of the blocks looks like this
I need to change only the name of the class that I clicked, I think I need to use the index, but I don't quite understand how to reolize it
export default function SelectGradientTheme(props) {
const resultsRender = [];
const [borderColor, setBorderColor] = useState(false);
const setBorder = () => {
setBorderColor(!borderColor)
}
const borderColorClassName = borderColor ? "selectBorder" : null;
for (var i = 0; i < GradientThemes.length; i += 3) {
resultsRender.push(
<div className={"SelectThemePictures_Separator"}>
{
GradientThemes.slice(i, i + 3).map((col, index) => {
return (
<div key={index} className={borderColorClassName} onClick={() => props.SideBarPageContent(col)|| setBorder()}>
</div>
);
})
}
</div>
)
}
return (
<div className="SelectThemeWrapper">
{resultsRender}
</div>
);
};
You can remember the selected index
Please reference the following code:
export default function SelectGradientTheme(props) {
const resultsRender = [];
const [selectedIndex, setSelectedIndex] = useState(false);
const setBorder = (index) => {
setSelectedIndex(index);
};
for (var i = 0; i < GradientThemes.length; i += 3) {
resultsRender.push(
<div className={"SelectThemePictures_Separator"}>
{
GradientThemes.slice(i, i + 3).map((col, index) => {
return (
<div key={index}
className={index === selectedIndex ? 'selectBorder' : null}
onClick={() => props.SideBarPageContent(col)|| setBorder(index)}>
</div>
);
})
}
</div>
)
}
return (
<div className="SelectThemeWrapper">
{resultsRender}
</div>
);
};
Related
I have a page with some dropdown menu's used to search some content, the dropdown is a non-functional component. The page is a listsing page. Not important but gives some context.
I do some calculation on the listing page and update the state, then I pass this state into the Dropdown component. However, I'm getting an infinite loop and I'm not sure how to stop it or where I'm going wrong.
my listing page is here:
constructor(props){
super(props)
let industryList = this.createList(this.props.data.mainYaml.caseStudiesDropdowns[0].items)
let areaList = this.createList(this.props.data.mainYaml.caseStudiesDropdowns[1].items)
let techniqueList = this.createList(this.props.data.mainYaml.caseStudiesDropdowns[2].items)
this.state = {
industry: "All Industries",
area: "All Areas",
technique: [],
industries: industryList,
areas: areaList,
techniques: techniqueList
}
}
createList = (listItems) => {
let listArr = []
listItems.forEach((item) => {
let obj = {
name: item,
disabled: false
}
listArr.push(obj)
})
return listArr
}
filterCaseStudies = (caseStudies) => {
const filterIndustry = (caseStudies) => {
if (this.state.industry == "All Industries") {
return caseStudies
} else {
return caseStudies.filter((study) => study.node.industry == this.state.industry)
}
}
const filterArea = (caseStudies) => {
if (this.state.area == "All Areas") {
return caseStudies
} else {
return caseStudies.filter((study) => study.node.area == this.state.area)
}
}
const filterTechnique = (caseStudies) => {
if (this.state.technique.length === 0) {
return caseStudies
} else {
let matchedStudies = []
caseStudies.forEach((study) => {
let count = 0;
let techCount = study.node.technique.length - 1;
study.node.technique.forEach((item, i) => {
this.state.technique.forEach((selectedItems) => {
if (selectedItems == item) {
count++;
return
}
})
if (i == techCount && count > 0) {
study.node.count = count
matchedStudies.push(study)
}
})
})
matchedStudies.sort((a, b) => b.node.count - a.node.count);
return matchedStudies;
}
}
let industryMatches = filterIndustry(caseStudies)
let areaMatches = filterArea(industryMatches)
this.filterDropdowns(areaMatches)
let techniqueMatches = filterTechnique(areaMatches)
return techniqueMatches;
}
filterDropdowns = (filteredCaseStudies) => {
console.log(filteredCaseStudies)
let disabledIndustries = [];
let disabledAreas = [];
let disabledTechniques = [];
this.state.industries.forEach((industry) => {
let obj = {
name: industry.name
}
if (industry.name == "All Industries") {
console.log(industry.name)
obj.disabled = false;
disabledIndustries.push(obj);
} else {
obj.disabled = true;
filteredCaseStudies.forEach((study) => {
if (study.node.industry == industry.name) {
obj.disabled = false;
}
})
disabledIndustries.push(obj);
}
})
console.log(disabledIndustries)
this.setState({industries: disabledIndustries})
}
getCaseStudies = (caseStudies) => {
let filteredCaseStudies = this.filterCaseStudies(caseStudies)
if (filteredCaseStudies.length > 0) {
return filteredCaseStudies.map((study, i) => {
return (
<div key={i} className="col-lg-4 col-md-6 col-12 px-4 mb-5">
<CaseStudyListItem
data={study.node}
className="CaseStudyListItem--lg"
index={i}/>
</div>
)
})
} else {
return (
<div className="col-12 px-4 mb-5">
<h4>We're Sorry!</h4>
<p>We can't seem to find any case studies that match your search. Please try other search terms.</p>
</div>
)
}
}
dropdownChange = (selected, name) => {
this.setState({[name]: selected})
}
render () {
console.log(this.state)
return (
<Layout bodyClass="k-reverse-header">
<div className="CaseStudies">
<section className="CaseStudies__header k-bg--grey">
<div className="container-fluid">
<div className="d-flex k-row">
<div className="col-12 px-4">
<DropdownSelect className="CaseStudies__search-industry mb-4" data={this.props.data.mainYaml.caseStudiesDropdowns[0]} list={this.state.industries} selected={this.dropdownChange} />
<DropdownSelect className="CaseStudies__search-area mb-4" data={this.props.data.mainYaml.caseStudiesDropdowns[1]} list={this.state.areas} selected={this.dropdownChange} />
</div>
</div>
</div>
</section>
<section className="CaseStudies__list">
<div className="container-fluid">
<div className="d-flex flex-wrap k-row">
{this.getCaseStudies(this.props.data.allCaseStudiesYaml.edges)}
</div>
</div>
</section>
</div>
</Layout>
)
}
}
I believe the issue happens as I pass the state into the Dropdown component, it is also updated in the filterDropdowns function. The Dropdown component code is as follows.
const DropdownSelect = ({ data, className, list, selected}) => {
const [isActive, setActive] = useState(false);
const [activeItem, changeActiveItem] = useState(data.placeholder);
const ref = useRef();
useEffect(() => {
const checkIfClickedOutside = (e) => {
// If the menu is open and the clicked target is not within the menu,
// then close the menu
if (isActive && ref.current && !ref.current.contains(e.target)) {
setActive(false)
}
}
document.addEventListener("mousedown", checkIfClickedOutside)
return () => {
// Cleanup the event listener
document.removeEventListener("mousedown", checkIfClickedOutside)
}
}, [isActive])
const toggleClass = () => {
setActive(!isActive);
}
const buildDropdown = () => {
const splitArr = (arr, len) => {
let chunks = [], i = 0, n = arr.length;
while (i < n) {
chunks.push(arr.slice(i, i += len));
}
return chunks;
}
const buildList = (items) => {
return items.map((item, i) =>
<li
key={i}
className={`DropdownSelect__list-item ${activeItem == item.name ? "active" : ""} ${item.disabled ? "disabled" : ""}`}
onClick={() => itemClicked(item.name, selected, data.name)}
>
{item.name}
</li>
)
}
const itemClicked = (item, selected, search) => {
changeActiveItem(item)
selected(item, search)
}
const arrLen = list.length < 10 ? 3 : 4;
const listsArr = splitArr(list, arrLen);
return listsArr.map((list, i) =>
<ul key={i} className="DropdownSelect__list">
{buildList(list)}
</ul>
)
}
return (
<div className={`DropdownSelect ${className ? className : ''}`} ref={ref}>
<div
className={`DropdownSelect__button ${isActive ? "active" : ""}`}
onClick={toggleClass}
>
{activeItem == null ? data.placeholder : activeItem}
</div>
<div className={`DropdownSelect__list-wrapper ${isActive ? "active" : ""}`}>{buildDropdown}</div>
</div>
)
}
export default DropdownSelect
I feel like i could have all of my state in the listing page but then the Dropdown component is pointless as it wouldn't be self sufficient and usable elsewhere.
I guess I want to know how I break this loop but also what are my bad practices here? ie am I using state wrongly?
Any help greatly appreciated!
PS Here's the React error i get
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
If I'm reading this right...
In your render, you have {this.getCaseStudies(this.props.data.allCaseStudiesYaml.edges)}. getCaseStudies calls filterCaseStudies, which calls filterDropdowns, which has a setState in it. When a setState occurs, the page re-renders, causing the page to go through all those function calls again, another setState occurs, the page re-renders again, forever, causing an infinite loop.
You'll have to re-write your code somewhat. You could possibly use that state to store the data in a different format, like an array, and map the data in your render?
I am practising react and I have this task to build an interface where a button can add elements, another button can remove element and two oter butttons can move each element up or down stepwise like this:
I have been able to make buttons add and remove element but changing positions of elements has been giving me some headache. Below is my code:
const [inputList, setInputList] = useState([{ inputBox: '' }]);
const handleInputChange = (e, index) => {
const { name, value } = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
const handleAddClick = () => {
setInputList([...inputList, { inputBox: '' }]);
};
const upPosition = () => {
console.log('up');
};
const downPosition = () => {
console.log('down');
};
return (
<div>
<h3>Add Elements</h3>
<button onClick={handleAddClick}>+</button>
{inputList.map((x, i) => {
return (
<div className='box inputBox' key={i}>
<input
name='inputBox'
value={x.inputBox}
onChange={(e) => handleInputChange(e, i)}
/>
<button onClick={() => upPosition()}>
<i className='fas fa-long-arrow-alt-up'></i>
</button>
<button onClick={() => downPosition()}>
<i className='fas fa-long-arrow-alt-down'></i>
</button>
<button className='mr10' onClick={() => handleRemoveClick(i)}>
<i className='fas fa-times'></i>
</button>
</div>
);
})}
</div>
);
You can make use of splice here
Beside the shifting, you have to handle the 2 cases also, where you can't go up if the index is 0 and you can't go down if the index is inputList.length - 1
NOTE: To handle the both cases, I've disabled the buttons because that would be more meaningful to let the end user know that this button is disable so you can't go up or down.
Live Demo
const upPosition = ( index ) => {
const copy = { ...inputList[index] };
setInputList( ( oldState ) => {
const newState = oldState.filter( ( o, i ) => i !== index );
newState.splice( index - 1, 0, copy );
return newState;
} )
};
const downPosition = (index) => {
const copy = { ...inputList[index] };
setInputList( ( oldState ) => {
const newState = oldState.filter( ( o, i ) => i !== index );
newState.splice( index + 1, 0, copy );
return newState;
} )
};
You should use the splice method which can do insertions as well
const upPosition = (indexToMove) => {
if (indexToMove === 0) return;
const list = [...inputList];
const itemToMove = list.splice(indexToMove, 1)[0];
list.splice(indexToMove-1, 0, itemToMove)
setInputList(list)
};
const downPosition = (indexToMove) => {
if (indexToMove === inputList.length - 1) return;
const list = [...inputList];
const itemToMove = list.splice(indexToMove, 1)[0];
list.splice(indexToMove+1, 0, itemToMove)
setInputList(list)
};
I think you can achieve with destructuring:
const [inputList, setInputList] = useState([{ inputBox: '' }]);
const handleInputChange = (e, index) => {
const { name, value } = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
const handleAddClick = () => {
setInputList([...inputList, { inputBox: '' }]);
};
const upPosition = (i) => {
if (i > 0) {
const temp = [...inputList];
[temp[i], temp[i - 1]] = [temp[i - 1], temp[i]];
setInputList(temp);
}
};
const downPosition = (i) => {
if (i < inputList.length - 1) {
const temp = [...inputList];
[temp[i], temp[i + 1]] = [temp[i + 1], temp[i]];
setInputList(temp);
}
};
return (
<div>
<h3>Add Elements</h3>
<button onClick={handleAddClick}>+</button>
{inputList.map((x, i) => {
return (
<div className="box inputBox" key={i}>
<input
name="inputBox"
value={x.inputBox}
onChange={(e) => handleInputChange(e, i)}
/>
<button onClick={(e) => upPosition(i)}>
<i className="fas fa-long-arrow-alt-up"></i>
</button>
<button onClick={() => downPosition(i)}>
<i className="fas fa-long-arrow-alt-down"></i>
</button>
<button className="mr10" onClick={() => handleRemoveClick(i)}>
<i className="fas fa-times"></i>
</button>
</div>
);
})}
</div>
);
Im doing a function(clearGame) to change all backgroundColor of the numbers i get in "NumbersParent". I tried using querySelector but all i get is "null" from changeStyle.
const newBet: React.FC = () => {
const clearGame = () => {
let spliceRangeJSON = gamesJson[whichLoteriaIsVar].range;
for (let i = 0; i <= spliceRangeJSON; i++) {
const changeStyle = document.querySelector<HTMLElement>('data-key');
changeStyle!.style.backgroundColor = '#ADC0C4';
}
};
const NumbersParent = (props: any) => {
const [numbersColor, setNumbersColor] = useState('#ADC0C4');
const changeButtonColor = () => {
if (numbersColor === '#ADC0C4') {
setNumbersColor(gamesJson[whichLoteriaIsVar].color);
} else {
setNumbersColor('#ADC0C4');
}
};
return (
<Numbers
color={numbersColor}
onClick={changeButtonColor}
>
{props.children}
</Numbers>
);
};
} return(
<NumbersContainer>
{numbersList.map((num) => (
<NumbersParent key={num} id={num} data-key={num}>
{num}
</NumbersParent>
))}
</NumbersContainer>
<Button onClick={clearGame}>Clear Game</Button
)
I am using React map and I am getting the following error
TypeError: Cannot read property 'PreviewThemeSideBar' of undefined
I don't understand in any way why I get undefined because I checked the imported component where I pass props and there I get all the data, see
SeleceColorsTheme.js
export default function SelectColorsTheme(props) {
const groupSize = 3;
const [selectedIndex, setSelectedIndex] = useState(false);
const setBorder = (index) => {
setSelectedIndex(index);
};
const rows = SideBarColors.map(function (col, index) {
const selectBorder = classNames({
'builtin_theme_preview': true,
'selectBorder': index === selectedIndex ? 'selectBorder' : null
});
// map content to html elements
return <SelectThemeContent {...props} selectBorder={selectBorder}
col={col} setBorder={setBorder} index={index}/>
}).reduce(function (r, element, index) {
// create element groups with size 3, result looks like:
// [[elem1, elem2, elem3], [elem4, elem5, elem6], ...]
index % groupSize === 0 && r.push([]);
r[r.length - 1].push(element);
return r;
}, []).map(function (rowContent) {
// surround every group with 'row'
return <div className="SelectThemePictures_Separator">{rowContent}</div>;
});
return <div className="container">{rows}</div>;
};
SelectThemeContent.js
export default function SelectThemeContent(props) {
const selectBorder = props.selectBorder;
const col = props.col;
const setBorder = props.setBorder;
const index = props.index;
return(
<div className={selectBorder} key={index} onClick={() => props.SideBarPageContent(col) || setBorder(index)}>
<div style={{ background: col.PreviewThemeSideBar }} className="builtin_theme_preview__nav">
<div className="builtin_theme_preview__search" />
...
</div>
</div>
);
}
I just added a check condition to each props, example
<div style={{ background: col && col.PreviewThemeSideBar }}</div>
Array not getting cleared to null or empty in setState on click in react.
When I click on the submit button, the array must be set to []. It is setting to [], but on change the previous array of items comes into the array.
let questions = [];
let qns = [];
class App extends Component {
constructor(props) {
super(props);
this.state = {
btnDisabled: true,
//questions: [],
};
}
changeRadioHandler = (event, j) => {
this.setState({ checked: true });
const qn = event.target.name;
const id = event.target.value;
let idVal = this.props.dat.mat.opts;
let text = this.props.dat.mat.opt;
let userAnswer = [];
for (let j = 0; j < text.length; j++) {
userAnswer.push(false);
}
const option = text.map((t, index) => ({
text: t.text,
userAnswer: userAnswer[index],
}));
const elIndex = option.findIndex((element) => element.text === id);
const options = { ...option };
options[elIndex] = {
...options[elIndex],
userAnswer: true,
};
const question = {
options,
id: event.target.value,
qn,
};
questions[j] = options;
qns = questions.filter((e) => {
return e != null;
});
console.log(qns, qns.length);
this.setState({ qns });
if (qns.length === idVal.length) {
this.setState({
btnDisabled: false,
});
}
};
submitHandler = () => {
console.log(this.state.qns, this.state.questions);
this.setState({ qns: [] }, () =>
console.log(this.state.qns, this.state.questions)
);
};
render() {
return (
<div class="matrix-bd">
{this.props.dat.mat && (
<div class="grid">
{this.props.dat.mat.opts.map((questions, j) => {
return (
<div class="rows" key={j}>
<div class="cell main">{questions.text}</div>
{this.props.dat.mat.opt.map((element, i) => {
return (
<div class="cell" key={i}>
<input
type="radio"
id={j + i}
name={questions.text}
value={element.text}
onChange={(event) =>
this.changeRadioHandler(event, j)
}
></input>
<label htmlFor={j + i}>{element.text}</label>
</div>
);
})}
</div>
);
})}
</div>
)}
<div>
<button
type="button"
class="btn btn-primary"
disabled={this.state.btnDisabled}
onClick={this.submitHandler}
>
SUBMIT
</button>
</div>
</div>
);
}
}
export default App;
On button click submit, the array must be set to [] and when on change, the value must be set to the emptied array with respect to its index.
changeRadioHandler = (event, j) => {
// the better way is use local variable
let questions = [];
let qns = [];
...
}
submitHandler = () => {
console.log(this.state.qns, this.state.questions);
this.setState({ qns: [] }, () =>
console.log(this.state.qns, this.state.questions)
)}
// clear the previous `qns`
// if u use local variable. you don't need those lines
// this.qns = []
// this.questions = []
}
Finally, found out the solution.
After adding componentDidMount and setting questions variable to null solved my issue.
componentDidMount = () => {
questions = [];
};
Thanks all for your efforts and responses!