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>
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 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>
);
};
I am trying to create a div for each item in an array that is a property of this.state
However, I am getting Cannot read property 'map' of undefined on the line, return outOfBudget.values.map((val, j)
Most of the posts on this subject have an issue because the data doesn't actually exist. I tried their solutions by wrapping the problematic line in an if(outOfBudget) statement, but the error persisted. I also log outOfBudget to console and see that it indeed exists.
Am I defining it incorrectly?
const BrokeBudget = ({outOfBudget}) => {
return outOfBudget.values.map((val, j) => {
return (
<div>
<p>{val.name}</p>
<p>{val.value}</p>
</div>
);
});
};
class Budget extends React.Component {
state = {
remainingBudget: 1600,
data,
pieChartData: [],
outOfBudget: []
};
handleInputChange = event => {
let { value, id, name } = event.target;
value = parseInt(value, 10);
const selectedQuestions = Object.assign(
{},
this.state.data.selectedQuestions
);
if (!selectedQuestions[name]) {
selectedQuestions[name] = {};
}
selectedQuestions[name][id] = value;
let newBudget = this.state.remainingBudget - value;
if( newBudget >= 0){
let pieSlice =
{
x: name,
y: value
};
console.log(pieSlice);
this.setState({
data: {
...this.state.data,
selectedQuestions
},
remainingBudget: newBudget,
pieChartData: this.state.pieChartData.concat(pieSlice),
});
}
else{
let beyondBudget = {genre: name, amount: value}
this.setState({
data: {
...this.state.data,
selectedQuestions
},
remainingBudget: newBudget,
pieChartData: this.state.pieChartData,
outOfBudget: {...this.state.outOfBudget, beyondBudget}
});
}
};
render() {
const { data, remainingBudget, pieChartData, outOfBudget } = this.state;
const questions = data.questions;
return (
<div>
{questions.map((q, i) =>
<UL key={i}>
<li>
<h4>{q.text}</h4>
</li>
<li>
<Options
state={this.state}
q={q}
i={i}
handler={this.handleInputChange}
/>
</li>
</UL>
)}
{Object.keys(data.selectedQuestions).length === 3 &&
<div>
<VictoryPie
colorScale = "blue"
data = {this.state.pieChartData}
labels= {d => `${d.x}: ${d.y}%`}
style={{ parent: { maxWidth: '50%' } }}
/>
< BrokeBudget
outOfBudget={outOfBudget}
/>
</div>
}
</div>
);
}
}
Please ignore any strange cases (like UL I use Emotion for styling)
What is .values ?
Looks like you need return outOfBudget.map((val, j) => { without the .values
Hope this helps.
I'm getting an error that i'm missing key prop for my map iteration.Got confused where i'm missing one . I have a map inside map.Could you please help me
displayData() {
const { data, index } = this.state;
let sortedData = data[index].settings.map((item, id) => {
const { _init_ } = item.settings;
return _init_.map((message, index) => {
const { message_content } = message;
return message_content === undefined ? null : (
<>
<div>
<div key={index} className="settings-message">
{message_content}
</div>
</div>
<div>yes</div>
</>
);
});
});
return sortedData;
}
The key should be on the parent div.
return message_content === undefined ? null : (
<div key={index}>
<div className="settings-message">
{message_content}
</div>
</div>
)
Your top level component needs a unique key. Use explicit fragment syntax and add the key to the fragment,
displayData() {
const { data, index } = this.state;
let sortedData = data[index].settings.map((item, id) => {
const { _init_ } = item.settings;
return _init_.map((message, index) => {
const { message_content } = message;
return message_content === undefined ? null : (
<React.Fragment key={index}>
<div>
<div className="settings-message">{message_content}</div>
</div>
<div>yes</div>
</React.Fragment>
);
});
});
return sortedData;
}
ps. You may have one more div than you actually need
I'm mapping an array, and displaying them as a button. When users clicks on button i'm storing index and displaying data depending on that index. But there is some strange behavior. When I click on first button it does not store index in state. Only after clicking on other buttons , it's starts saving , but wrong index. What could be the problem?
displayButtons() {
const { data } = this.state
let sortedButtons = data.map((items, idx) => {
return (
<Button
key={idx}
className="project-btn"
primary
onClick={() => this.setState({ index: idx })}
>
{items.title}
</Button>
)
})
return sortedButtons
}
displayData() {
const { data, index } = this.state
let sortedData = data[index].settings.map((item, id) => {
const { _init_ } = item.settings
return _init_.map((message, index) => {
const { message_content } = message
return message_content === undefined ? null : (
<div key={index}>
<div>
<div className="settings-message">{message_content}</div>
</div>
<div>yes</div>
</div>
)
})
})
return sortedData
}