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.
Related
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!
I am updating properties of a state element inside of map
computePiePercentages(){
var denominator = 1600
if (this.state.total < 1600){
denominator = this.state.total
}
return this.state.pieChartData.map((item, index) => {
return {
...item,
y: Number((item.y / denominator).toFixed(1)) * 100
}
})
}
However, when I display the chart using pieChartData - y hasn't updated. I checked my math to make sure I am setting to the correct value inside computePiePercentages
Is the map function asynchronous like setState? How can I make sure to wait for the update to happen before I display my results?
Here is the rest of relevant code:
class Budget extends React.Component {
computePiePercentages(){
var denominator = 1600
if (this.state.total < 1600){
denominator = this.state.total
}
return this.state.pieChartData.map((item, index) => {
return {
...item,
y: Number((item.y / denominator).toFixed(1)) * 100
}
})
}
computeTotals(){
var runningTotal = 0
var pieArray = []
var beyondBudget = {}
Object.entries(this.state.data.selectedQuestions).map((element, j) => {
console.log("hi here")
console.log(element)
//return selectedQuestions.map((val, j) => {
const value = Object.values(element[1])[0]
const name = element[0]
runningTotal += value
if(runningTotal <= 1600){
let pieSlice =
{
x: name,
y: value
};
pieArray = pieArray.concat(pieSlice)
}
else {
if (Object.keys(beyondBudget).length == 0) {
beyondBudget[name] = {};
beyondBudget[name] = runningTotal - 1600;
let pieSlice =
{
x: name,
y: value - (beyondBudget[name])
};
pieArray = pieArray.concat(pieSlice)
}
if (!beyondBudget[name]) {
beyondBudget[name] = {};
}
if (Object.keys(beyondBudget).length > 1) {
beyondBudget[name] = value;
}
}
});
this.setState({
pieChartData: pieArray,
total: runningTotal,
beyondBudget: beyondBudget,
}, () => {
this.computePiePercentages();
});
}
render() {
const {
data,
pieChartData,
beyondBudget,
showResults,
total
} = 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>
))}
<button onClick={(event) => {
this.computeTotals();
this._showResults(true);
}}>Click</button>
{console.log(this.state.showResults)}
{this.state.showResults &&
(<div>
<VictoryPie
colorScale="blue"
data={pieChartData}
labels={d => `${d.x}: ${d.y}%`}
style={{ parent: { maxWidth: '50%' } }}
/>
{Object.keys(beyondBudget).length > 0 && (
<div>
<Table>
<tbody>
<tr>
<th>Out of Budget</th>
</tr>
<BrokeBudget
beyondBudget={beyondBudget}
/>
</tbody>
</Table>
</div>
)}
</div>
)
}
</div>
);
}
}
As others have mentioned, an array's .map() function returns a new array without changing the old array. So this.computePiePercentages() as it currently stands, creates a new array based on this.state.pieChartData and returns that new array. This action does not change this.state.pieChartData.
You're calling this.computePiePercentages() from the callback function of this.setState(). This is just a function, it has no special properties other than that it's called when the setState() is done changing the state. So to update the state further inside computePiePercentages() you need to call setState() again.
There are two options:
Update the state in the callback function, using the return value of this.computePiePercentages:
this.setState({
pieChartData: pieArray,
total: runningTotal,
beyondBudget: beyondBudget,
}, () => {
this.setState({
pieChartData: this.computePiePercentages()
});
});
Update the state in this.computePiePercentages:
this.setState({
pieChartData: this.state.pieChartData.map((item, index) => {
return {
...item,
y: Number((item.y / denominator).toFixed(1)) * 100
}
})
});
i have implemented search filter to my react app, everything worked fine till i made select tag, to change search filter criteria.
class BookList extends Component {
state = {
search: '',
selectedValue: 'name',
options: [
{
name: 'Name',
value: 'name',
},
{
name: 'Author',
value: 'author',
},
{
name: 'ISBN',
value: 'isbn',
}
]
}
updateSearch (e) {
this.setState({search: e.target.value});
}
selectedValueHandler (e) {
this.setState({selectedValue: e.target.value});
}
render () {
if (this.state.selectedValue === 'name') {
let filteredBooks = this.props.books.filter(book => {
return book.name.toLowerCase().indexOf(this.state.search) !== -1;
})
} else if (this.state.selectedValue === 'author') {
let filteredBooks = this.props.books.filter(book => {
return book.author.toLowerCase().indexOf(this.state.search) !==
-1;
})
} else if (this.state.selectedValue === 'isbn') {
let filteredBooks = this.props.books.filter(book => {
return book.isbn.indexOf(this.state.search) !== -1;
})
}
return (
<div>
<div className='SearchInput'>
<input type='text'
value={this.state.search}
onChange={this.updateSearch.bind(this)} />
<select
id="searchSelect"
name="searchSelect"
onChange={this.selectedValueHandler.bind(this)} >
{this.state.options.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</select>
</div>
<div className='BookList'>
<ul>
{filteredBooks.map(book => {
return <Book key={book.book_id} name={book.name} author={book.author} isbn={book.isbn} />
})}
</ul>
</div>
</div>
)
}
};
export default BookList;
when i implement this code i am getting error: Line 69: 'filteredBooks' is not defined no-undef.
Tried to put this.state.selectedValue instead of name but it also doesn't work.
Any ideas how to fix issue?
let variables are locally scoped to the nearest wrapping curly braces. Define the variable above the if statements.
render () {
let filteredBooks;
if (this.state.selectedValue === 'name') {
filteredBooks = this.props.books.filter(book => {
return book.name.toLowerCase().indexOf(this.state.search) !== -1;
})
...
Unrelated, here's one way you could shorten your code:
const { books } = this.props;
const { search } = this.state;
const filteredBooks = books.filter(book =>
book[search].toLowerCase().includes(search)
)
I can't seem to pass this handler correctly. TabItem ends up with undefined for onClick.
SearchTabs
export default class SearchTabs extends Component {
constructor(props) {
super(props)
const breakpoints = {
[SITE_PLATFORM_WEB]: {
displayGrid: true,
autoFocus: true,
},
[SITE_PLATFORM_MOBILE]: {
displayGrid: false,
autoFocus: false,
},
};
this.state = {
breakpoints,
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
tabs: null,
};
this.tabChanged = this.tabChanged.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
}
... more code
createTabs(panels) {
if(!panels) return;
const tabs = panels.member.map((panel, idx) => {
const { selectedTab } = this.props;
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.renderFilters(panel, idx, selectedTab);
return (
<TabItem
key={panelId}
classname={classname}
idx={idx}
content={item}
onClick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
tabChanged(idx, headline) {
const { selectedTab } = this.props;
const { selectedFilter } = this.state;
const selectedFilterIdx = _.get(selectedFilter, 'idx', null);
if (selectedTab !== idx) {
this.props.resetNextPage();
this.props.setTab(idx, selectedFilterIdx, headline);
this.closeDropdown();
}
}
render() {
// const { panels, selectedTab } = this.props;
// if (!panels || panels.length === 0) return null;
//
//
// const { tabs, selectedTab } = this.props;
return (
<div>
<ul>{this.state.tabs}</ul>
</div>
);
}
}
export const TabItem = ({ classname, content, onClick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onClick} >{content}</li>
);
so in TabItem onClick={onClick} ends up with undefined for onClick.
More info
here's how this used to work, when this was a function in the parent Container:
// renderDefaultTabs() {
// const { panels, selectedTab } = this.props;
//
// if (!panels || panels.length === 0) return;
//
// let filter = null;
//
// const tabs = panels.member.map((panel, idx) => {
// const { id: panelId, headline } = panel;
// const url = getHeaderLogo(panel, 50);
// const item = url ?
// <img src={url} alt={headline} /> : headline;
// const classname = classNames([
// searchResultsTheme.tabItem,
// (idx === selectedTab) ? searchResultsTheme.active : null,
// ]);
//
// filter = (idx === selectedTab) ? this.renderFilters(panel) : filter;
//
// return (
// <li
// key={panelId}
// className={classname}
// onClick={() => {
// this.tabChanged(idx, headline);
// }}
// >
// {item}
// </li>
// );
// });
So I extracted that out to that SearchTabs including moving the tabChange d method to my new SearchTabs component. And now in the container the above now does this:
renderDefaultTabs() {
const {
onFilterClick,
panels,
resetNextPage,
selectedTab,
selectedFilter,
isDropdownOpen,
} = this.props;
return (<SearchTabs
panels={panels}
...
/>);
}
Note: renderDefaultTabs() is sent as a prop to in the render() of the container and the Search calls it back thus rendering it in the Search's render():
Container
render() {
return (
<Search
request={{
headers: searchHeaders,
route: searchRoute,
}}
renderTabs={this.renderDefaultTabs}
renderSearchResults={this.renderSearchResults}
handleInputChange={({ input }) => {
this.setState({ searchInput: input });
}}
renderAltResults={true}
/>
);
}
Search is a shared component our apps use.
Update
So I mentioned that the Container's render() passes the renderDefaultTabs function as a prop to <Search />. Inside <Search /> it ultimately does this: render() { <div>{renderTabs({searchResults})}</div>} which calls the container's renderDefaultTabs function which as you can see above, ultimately renders
So it is passing it as a function. It's just strange when I click a TabItem, it doesn't hit my tabChanged function whatsoever
Update
Christ, it's hitting my tabChanged. Errr..I think I'm good. Thanks all!
onClick={this.tabChanged(idx, headline)}
This is not a proper way to pass a function to child component's props. Do it like (though it is not recommended)
onClick={() => this.tabChanged(idx, headline)}
UPDATE
I want to add more explanation. By onClick={this.tabChanged(idx, headline)}, you are executing tabChanged and pass its returned value to onClick.
With your previous implementation: onClick={() => { this.tabChanged(idx, headline); }}, now onClick will be a function similar to:
onClick = {(function() {
this.tabChanged(idx, headline);
})}
So it works with your previous implementation.
With your new implementation, onClick={() => this.tabChanged(idx, headline)} should work
I need your fresh eyes to help me.
I have a set of answers in my array which I shuffle on the first render.
My problem here, is that I know if i am clicking on one of the answer, the setState will re-render and consequently re-shuffle my array which i dont want.
You can have a look at my code below:
export default class extends React.Component {
constructor(props) {
super(props)
this.state = {
user: this.props.user,
token: this.props.token,
data: this.props.data,
count: 0,
select: undefined
}
this.changeQuestion = this.changeQuestion.bind(this);
this.onCorrect = this.onCorrect.bind(this);
this.onFalse = this.onFalse.bind(this);
}
static async getInitialProps({req, query}) {
const id = query.id;
const authProps = await getAuthProps(req, 'Country/Questions?theory=' + id)
return authProps
}
componentDidMount() {
if (this.state.user === undefined) {
Router.push('/login')
}
}
changeQuestion() {
this.setState({
count: this.state.count + 1,
select: undefined
})
}
onCorrect() {
this.setState({
select: true
})
}
onFalse() {
this.setState({
select: true
})
}
mixAnswers() {
const answer = this.props.data.Properties.Elements
const answers = answer[this.state.count].Properties.Answers
const answersObj = answers.reduce((ac, el, i) => {
ac.push(
<p key={i} onClick={i === 0
? this.onCorrect
: this.onFalse} className={i === 0
? 'exercices__answers--correct'
: 'exercices__answers--false'}>{el}</p>
)
return ac
}, [])
const answersShuffled = answersObj.sort(() => 0.5 - Math.random())
return answersShuffled;
}
render() {
const {user, token, data} = this.state
const answer = this.props.data.Properties.Elements
const answers = answer[this.state.count].Properties.Answers
return (
<div>
{user !== undefined
? <Layout user={this.state.user}>
<div>
{answer[this.state.count].Properties.Sources !== undefined
? <img src={answer[this.state.count].Properties.Sources[0].URL}/>
: ''}
<h1>{answer[this.state.count].Properties.Question}</h1>
{this.mixAnswers().map((el, i) => <p key={i} onClick={el.props.onClick} className={this.state.select !== undefined
? el.props.className
: ''}>{el.props.children}</p>)
}
<p>{answer[this.state.count].Properties.Description}</p>
</div>
<button onClick={this.changeQuestion}>Next Question</button>
</Layout>
: <h1>Loading...</h1>}
</div>
)
}
}
Obviously, the way I am using the 'this.mixAnswers()' method is the issue. How can I prevent it to re-render then re-shuffle this array of questions.
PS: dont pay attention about onCorrect() and onFalse().
You should make sure the logic that shuffle the answers is called only once, you can get this behavior on ComponentWillMount or ComponentDidMount, then you save them in the state of the component and in the render function instead of
{this.mixAnswers().map((el, i) => <p key={i} onClick={el.props.onClick} className={this.state.select !== undefined
? el.props.className
: ''}>{el.props.children}</p>)
}
You use this.state.answers.map()...