I have two tabs that contains some bulletins and newses, respectively. And a badge on each tab to tell if all of the items has been viewed.
If all of bulletins or newses has been viewed, then the badge on that tab will hide, otherwise the badge will show up.
All needed calculations were defined in function checkBulletinHasNew and checkNewsesHasNew. But when I opened it up in browser, it crashed.
I'm pretty sure the causes of crash is the this.setState in these two function, because when I comment this.setState and replace it with console.log sentence, the browser works as usual.
How Can I fix it?
import React from 'react';
import {Tab, Tabs} from '../../../../../components/Tabs';
import {TitleBar} from '../../../../../components/TitleBar';
import List from './List.jsx'
import ListItem from './ListItem.jsx'
class MsgCenter extends React.Component {
constructor() {
super()
this.checkBulletinHasNew = this.checkBulletinHasNew.bind(this)
this.checkNewsesHasNew = this.checkNewsesHasNew.bind(this)
this.state = {
bulletinHasNew: false,
newsesHasNew: false,
active: true
}
}
handleTabChanged() {
}
checkBulletinHasNew(bulletins) {
if (bulletins && bulletins.length > 0) {
for(var i = 0;i < bulletins.length;i++){
if (!bulletins[i].viewed){
this.setState({bulletinHasNew: true})
//console.log('bulletings has un-viewed')
return
}
}
this.setState({bulletinHasNew: false})
//console.log('bulletings are viewed')
return
}
}
checkNewsesHasNew(newses) {
if (newses && newses.length > 0) {
for(var i = 0;i < newses.length;i++) {
if(!newses[i].viewed){
this.setState({newsesHasNew: true})
//console.log('newses has un-viewed')
return
}
}
this.setState({newsesHasNew: false})
//console.log('newses are viewed')
return
}
}
componentWillUpdate(nextProps, nextState) {
this.checkBulletinHasNew(nextProps.bulletins.items)
this.checkNewsesHasNew(nextProps.newses.items)
}
componentDidMount(){
this.checkBulletinHasNew(this.props.bulletins.items)
this.checkNewsesHasNew(this.props.newses.items)
}
render() {
return (
<div>
<TitleBar title="Message Center"></TitleBar>
<Tabs showInkBar>
<Tab label="Bulletins" value={0} badge={this.state.bulletinHasNew ?
<span className="circleBadge">badge</span> :
null
}>
<List>
{
this.props.bulletins.items.map(function (item) {
return (
<ListItem item={item} key={'bulletin.' + item.id}></ListItem>
)
})
}
</List>
</Tab>
<Tab label="Newses" value={1} badge={this.state.newsesHasNew ?
<span className="circleBadge">badge</span> :
null
}>
<List>
{
this.props.newses.items.map(function (item) {
return (
<ListItem item={item} key={'news' + item.id}></ListItem>
)
})
}
</List>
</Tab>
</Tabs>
</div>
)
}
}
MsgCenter.defaultProps = {
activeSubject: 'bulletins',
bulletins: {
isFetching: false,
isRefreshing: false,
page: 1,
totalPage: 1,
items: [
{
id: 1,
title: 'This is bulletin 1',
publicDate: 1461513600000,
viewed: true
},
{
id: 2,
title: 'This is bulletin 2',
publicDate: 1461427200000,
viewed: true
},
{
id: 3,
title: 'This is bulletin 3',
publicDate: 1461340800000,
viewed: true
},
{
id: 4,
title: 'This is bulletin 4',
publicDate: 1461254400000,
viewed: true
}
]
},
newses: {
isFetching: false,
isRefreshing: false,
page: 1,
totalPage: 1,
items: [
{
id: 5,
title: 'This is news 1',
publicDate: 1458748800000,
viewed: false
},
{
id: 6,
title: 'This is news 2',
publicDate: 1458662400000,
viewed: false
},
{
id: 7,
title: 'This is news 3',
publicDate: 1458576000000,
viewed: true
},
{
id: 8,
title: 'This is news 4',
publicDate: 1458489600000,
viewed: true
},
]
}
}
module.exports = MsgCenter
The docs specifically state you shouldn't use set state in those lifecycle methods:
You cannot use this.setState() in this method. If you need to update
state in response to a prop change, use componentWillReceiveProps
instead.
I'm not sure exactly what it is doing, but I am guessing that setState triggers another "componentWillX" and in turn calls setState which triggers another "componentWillX" and in turn calls setState which triggers another "componentWillX" and in turn calls setState which triggers another "componentWillX" and in turn calls setState which ...
https://facebook.github.io/react/docs/component-specs.html
This is (probably) not an answer to the cause of your crash, but I suggest to change this:
checkBulletinHasNew(bulletins) {
if (bulletins && bulletins.length > 0) {
for(var i = 0;i < bulletins.length;i++){
if (!bulletins[i].viewed){
this.setState({bulletinHasNew: true})
//console.log('bulletings has un-viewed')
return
}
}
this.setState({bulletinHasNew: false})
//console.log('bulletings are viewed')
return
}
}
To this:
checkBulletinHasNew(bulletins) {
if (bulletins && bulletins.length > 0) {
var hasUnviewed = false;
for(var i = 0;i < bulletins.length;i++){
if (!bulletins[i].viewed){
hasUnviewed = true;
//console.log('bulletins have un-viewed')
}
}
this.setState({bulletinHasNew: hasUnviewed})
//console.log('bulletins are viewed')
}
}
Or even shorter, with an array.reduce():
checkBulletinHasNew(bulletins) {
if (bulletins && bulletins.length > 0) {
var hasUnviewed = bulletins.reduce(function(prevVal, curVal, i, array) {
return prevVal || !curVal.a;
},false);
this.setState({bulletinHasNew: hasUnviewed})
//console.log('bulletins are viewed')
}
}
To get a cleaner function that is guaranteed only to do only 1 state-update.
Same for the other similar method.
I presume your constructor should be like this. Otherwise you are setting the state twice.
constructor() {
super()
this.state = {
bulletinHasNew: false,
newsesHasNew: false,
active: true
}
This will result, the render method will render with default values that we set through the constructor. After that you can alter the values once the component mounted on the DOM.
You can remove componentWillUpdate and you can depend on componentDidMount to set the state. If you want to change setStatus once the component has been re-rendered use componentDidUpdate
Note: I would recommend to set both (bulletinHasNew,newsesHasNew) states together to avoid two render calls.
Hope this makes sense.
Thanks #Chris, that answer helps a lot, since I haven't read-through the docs carefully. That's true #Rajeesh Madambat, u can't call the setState method in those life cycle functions except for componentWillReceiveProps, however, that function doesn't be of any use to me here.
I'm new to reactjs, so when I want to control the appearance of a piece of component (in here is badge), all I come up with by reflex is to create a state to manage that. That's wrong because that state depends on props all the time.
Imagine this: I initial two states at the very beginning, but that's meaningless because the value of those two state depends on props.So I have to set those two state right after the component has received props. Which means there is no way to avoid that call-setstate-in-life-cycle trap.
If you have some value that depends on props all the time, don't make it as state(maybe props?), make it as computed value. And you compute those computed values in the render method. That problem happened to me just because I think in a wrong way in React.
So, with #wintvelt's great suggestion of code optimization, I can rewrite my code as below and it works as expected:
import React from 'react';
import {Tab, Tabs} from '../../../../../components/Tabs';
import {TitleBar} from '../../../../../components/TitleBar';
import List from './List.jsx'
import ListItem from './ListItem.jsx'
class MsgCenter extends React.Component {
constructor() {
super()
this.checkItemsHasUnviewed = this.checkItemsHasUnviewed.bind(this)
this.state = {
bulletinHasNew: false,
newsesHasNew: false,
active: true
}
}
checkItemsHasUnviewed(items) {
let hasUnViewed = false
if (items && items.length > 0) {
for (let i = 0; i < items.length; i++) {
if (!items[i].viewed) {
hasUnViewed = true
break
}
}
}
return hasUnViewed
}
render() {
let bulletinHasUnviewed = this.checkItemsHasUnviewed(this.props.bulletins.items)
let newsHasUnviewed = this.checkItemsHasUnviewed(this.props.newses.items)
return (
<div>
<TitleBar title="Message Center"></TitleBar>
<Tabs showInkBar>
<Tab label="Bulletin" value={0} badge={bulletinHasUnviewed ?
<span className="circleBadge"></span> :
null
}>
<List>
{
this.props.bulletins.items.map(function (item) {
return (
<ListItem item={item} key={'bulletin.' + item.id}></ListItem>
)
})
}
</List>
</Tab>
<Tab label="News" value={1} badge={newsHasUnviewed ?
<span className="circleBadge"></span> :
null
}>
<List>
{
this.props.newses.items.map(function (item) {
return (
<ListItem item={item} key={'news' + item.id}></ListItem>
)
})
}
</List>
</Tab>
</Tabs>
</div>
)
}
}
MsgCenter.defaultProps = {
activeSubject: 'bulletins',
bulletins: {
isFetching: false,
isRefreshing: false,
page: 1,
totalPage: 1,
items: [
{
id: 1,
title: 'This is bulletin 1',
publicDate: 1461513600000,
viewed: true
},
{
id: 2,
title: 'This is bulletin 2',
publicDate: 1461427200000,
viewed: true
},
{
id: 3,
title: 'This is bulletin 3',
publicDate: 1461340800000,
viewed: true
},
{
id: 4,
title: 'This is bulletin 4',
publicDate: 1461254400000,
viewed: false
}
]
},
newses: {
isFetching: false,
isRefreshing: false,
page: 1,
totalPage: 1,
items: [
{
id: 5,
title: 'This is news 1',
publicDate: 1458748800000,
viewed: false
},
{
id: 6,
title: 'This is news 2',
publicDate: 1458662400000,
viewed: false
},
{
id: 7,
title: 'This is news 3',
publicDate: 1458576000000,
viewed: true
},
{
id: 8,
title: 'This is news 4',
publicDate: 1458489600000,
viewed: true
},
]
}
}
module.exports = MsgCenter
Feel free to comment on this.
Related
I'm trying to change the page in MaterialTable but am unable to do so. Although, the page size functionality is working but the page change functionality isn't.
Here is my state:
constructor(props) {
super(props)
this.state = {
status: false,
message: "",
page: 0,
pageSize: 5,
}
}
And inside MaterialTable, I have this:
<MaterialTable
title=""
page={this.state.page}
totalCount={this.props.operations.ids ? this.props.operations.ids.length : 0}
columns={[
{
title: 'Sr No.', field: 'serialNumber', render: rowData => {
return _.findIndex(renderingData, { "id": rowData.id }) + 1
}
},
{ title: 'Time Stamp', field: 'date', render: rowData => { return moment(rowData.date).format("YYYY/MM/DD hh:mm a") } },
{ title: 'Details', field: 'name' },
{
title: 'View Details', field: 'viewDetails', render: rowData => {
return <Button
variant="contained"
color={"primary"}
onClick={() => this.props.setTab(rowData)}
>View</Button>
}
},
]}
onChangePage={(page, pageSize) => {
this.setState({ ...this.state, page, pageSize})
}}
data={renderingData}
/>
Let me know if any modification is required for this. I still haven't been able to solve the problem.
clicking on next button , you also need to increase the page number
onChangePage={(event,page ) => { this.setState({ ...this.state, page}) }}
also change
function handleChangeRowsPerPage(event) { setRowsPerPage(+event.target.value); }
Ok, so the problem was that the data that goes into Material Table was the same even after clicking the Next page button. I went around it by adding this in the data. The rest of the code is the same.
data={renderingData.slice(this.state.page*this.state.pageSize, this.state.page*this.state.pageSize + this.state.pageSize)}
This code makes sure that new data is present when you click on next page.
I have this component that has a Bootstrap Table and a Modal. User should be able to go into the modal and change the state of the same data for both the table and the modal; however, I am seeing that it is only changing the view in the modal but not the table?
Component with Table and Modal:
export class TableAndModal extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data,
showModal: false,
index: ""
}
};
this.setShow = this.setShow.bind(this);
this.handleShowAndChange = this.handleShowAndChange.bind(this);
}
columns = [{
dataField: "view",
text: "View"
formatter: (cell, row, rowIndex) => {
return (
<div>
<Button variant="info" onClick={() => this.setShow(rowIndex)}>View</Button>
</div>
);
}
},
{dataField: 'fullName', text: 'Full Name' },
{dataField: 'studentDesc', text: 'Student Description'},
{dataField: 'email', text: 'Email'},
{dataField: 'fullNotes', text: 'Full Notes'},
{dataField: 'edu', text: 'Education'},
{dataField: 'phone', text: 'Phone Number'},
{dataField: 'id', text: 'ID'}];
setShow(index) {
this.setState({
showModal: true,
index: index
});
}
handleShowAndChange = (name, value) => {
this.setState((prevState) => {
let newState = {...prevState};
newState[name] = value;
return newState;
});
};
render() {
return (
<div>
<BootstrapTable
hover
condensed={true}
bootstrap4={true}
keyField={'id'}
data={this.state.data.info}
columns={this.columns}
/>
<Modal data={this.state.data.info} index={this.state.index}
showModal={this.state.showModal} onChange={this.handleShowAndChange} />
</div>
);
}
}
Modal:
this.state = {
data: this.props.data
};
handleInfoChange = (index) => (name, value) => {
let info = this.state.data.info.slice();
info[index][name] = value;
this.props.onChange("info", info);
};
I am seeing that the state is being modified correctly. However, the table still has the same view with the old state data even though the state has been changed.
Can someone guide me on what I am doing wrong?
I think you should use props.data instead of this.props.data
constructor(props) {
super(props);
this.state = {
data: props.data,
showModal: false,
index: ""
}
}
In my application, I need something like:
When a questions value is null then the checkbox should be shown as indeterminate, otherwise should be checked or not-checked.
But the problem is that when I update the questions, it shows me the error:
TypeError: Cannot set property 'indeterminate' of null
My questions object in state is like this:
questions: [{
id: 1,
title: 'First Question',
answers: [
{
id: 2,
title: 'Java',
value: ''
},
{
id: 3,
title: 'Python',
value: ''
},
{
id: 4,
title: '.NET',
value: true
}
]
}]
So it means that the third checkbox should be checked, and other two should be shown as indeterminate.
See picture below:
So when I click on the first one, it should become unchecked,and after clicking it again, its value should be true and should become checked. And their value will never be '' ever, except that it can be the first time.
Here's the question.jsx
import React, { Component } from 'react';
class Question extends Component {
state = {
questions: []
}
componentDidMount() {
const questions = [{
id: 1,
title: 'First Question',
answers: [
{
id: 2,
title: 'Java',
value: ''
},
{
id: 3,
title: 'Python',
value: ''
},
{
id: 4,
title: '.NET',
value: true
}
]
}, {
id: 2,
title: 'Second Question',
answers: [
{
id: 5,
title: 'MongoDB',
value: ''
},
{
id: 6,
title: 'MSSQL',
value: ''
},
{
id: 7,
title: 'MySQL',
value: ''
}
]
}, {
id: 3,
title: 'Third Question',
answers: [
{
id: 8,
title: 'ReactJs',
value: ''
},
{
id: 9,
title: 'Angular',
value: ''
},
{
id: 10,
title: 'VueJs',
value: ''
}
]
}]
this.setState({
questions
})
}
setIndeterminate = (elm, value) => {
if (value !== '') {
elm.checked = value;
elm.indeterminate = false;
}
else {
elm.checkbox = false;
elm.indeterminate = true;
}
}
handleOnChange = ({ currentTarget: checkbox }) => {
var questions = [...this.state.questions];
questions.map(p => {
p.answers.map(a => {
if (a.id == checkbox.id) {
a.value = (a.value === '') ? false : !a.value;
return;
}
})
})
this.setState({
questions
})
}
render() {
const { questions } = this.state
return (
<div>
{questions.map(question =>
<div key={question.id} className='question-wrapper'>
<div className="row">
<h6 className='text-left'>{question.title}</h6>
</div>
{question.answers.map((answer, i) =>
<div key={answer.id} className="form-group row">
<div className="form-check">
<input onChange={this.handleOnChange} ref={elm => this.setIndeterminate(elm, answer.value)} value={answer.value} className="form-check-input" type="checkbox" id={answer.id} name={answer.id} />
<label className="form-check-label" htmlFor={answer.id}>
{answer.title}
</label>
</div>
</div>
)}
</div>
)}
</div>
);
}
}
export default Question;
How is that possible of happening since as you can see I am already setting the value of intermediate to either true or false?
SOLUTION
I removed that setIndeterminate function, and did this inside ref in input element:
<input onChange={this.handleOnChange} ref={elm => {
if (elm) {
elm.checked = (answer.value !== '') ? answer.value : false;
elm.indeterminate = (answer.value === '') ? true : false;
}
}} value={answer.value} className="form-check-input" type="checkbox" id={answer.id} name={answer.id} />
I guess the problem whas that I needed to add that if (elm) to check that first.
I found this solution here (Thanks to ROBIN WIERUCH for this awesome article ) and works fine for me:
We want to extend the functionality of this checkbox for handling a tri state instead of a bi state. First, we need to transform our state from a boolean to an enum, because only this way we can create a tri state:
const CHECKBOX_STATES = {
Checked: 'Checked',
Indeterminate: 'Indeterminate',
Empty: 'Empty',
};
and now we can use it in pur component:
const Checkbox = ({ label, value, onChange }) => {
const checkboxRef = React.useRef();
React.useEffect(() => {
if (value === CHECKBOX_STATES.Checked) {
checkboxRef.current.checked = true;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Empty) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = false;
} else if (value === CHECKBOX_STATES.Indeterminate) {
checkboxRef.current.checked = false;
checkboxRef.current.indeterminate = true;
}
}, [value]);
return (
<label>
<input ref={checkboxRef} type="checkbox" onChange={onChange} />
{label}
</label>
);
};
I am working on React Table. I am basically a beginner in React. I have a dashboard page where I display a React Table of 8 columns. I have a customize button which will open a popup page, this popup page has 8 check boxes allows me to show/hide those React columns. Initially all the check boxes in this popup page is set to true. When I uncheck a column that particular column get disabled.
There are images in the end to see what I am trying to do.
I will be using this logic for show hide columns (this question was asked by me two days back) -
How to show and hide some columns on React Table?
The React Table data is like this
const columns = [
{
Header: 'Column 1',
accessor: 'firstName',
},
{
Header: 'Column 2',
accessor: 'firstName',
},
{
Header: 'Column 3',
accessor: 'firstName',
},
{
Header: 'Column 4',
accessor: 'firstName',
},
{
Header: 'Column 5',
accessor: 'firstName',
},
{
Header: 'Column 6',
accessor: 'firstName',
},
{
Header: 'Column 7',
accessor: 'firstName'
},
{
Header: 'Column 8',
accessor: 'firstName',
}
];
The start of the dashboard page
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
filterState: {},
searchText: '',
isFilterOpen: false,
isCustomizedOpen: false,
isFiltered: false,
isSearched: false,
searchedTableData: [],
filteredTableData: [],
};
this.handleCustClickinv = this.handleCustClickinv.bind(this);
}
This is my code in the render function of my dashboard page for showing the customize button (this is written in parent dashboard page)
{this.state.isCustomizedOpen && <CustomizedView
filterState={this.state.filterState}
applyFilter={(values, clear) => { this.applyFilters(values, clear); }}
/>}
This is the code for the customize button (this is written in parent dashboard page)
<div className="custom-div-dashboard" onClick={() => { this.handleCustClickinv(); }}>
<div className='customize-view-dashboard'>Customized View </div>
</div>
This is function to handle the click on customize button (this is written in parent dashboard page)
handleFilterClickinv() {
if(this.state.isCustomizedOpen) {
this.setState({ isCustomizedOpen: false });
}
const currentState = this.state.isFilterOpen;
this.setState({ isFilterOpen: !currentState });
}
This is my entire popup page which will have 8 check boxes
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ActionCreators } from '../../../actions';
import './enqCustomizedView.scss';
import ButtonComponent from '../../shared/button/ButtonComponent';
import { CheckBox } from '../../shared/chkbox/CheckBox';
class CustomizedView extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, value: 'Column 1', isChecked: true },
{ id: 2, value: 'Column 2', isChecked: true },
{ id: 3, value: 'Column 3', isChecked: true },
{ id: 4, value: 'Column 4', isChecked: true },
{ id: 5, value: 'Column 5', isChecked: true },
{ id: 6, value: 'Column 6', isChecked: true },
{ id: 7, value: 'Column 7', isChecked: true },
{ id: 8, value: 'Column 8', isChecked: true },
]
};
this.handleCheckChildElement = this.handleCheckChildElement.bind(this);
}
handleClick() {
this.setState({ isChecked: !this.state.isChecked });
}
handleCheckChildElement(event) {
//let items = this.state.items;
let { items } = this.state;
items.forEach(items = () => {
if(items.value === event.target.value) {
items.isChecked = event.target.checked;
}
});
this.setState({ items });
const column1checked = items[0].isChecked;
console.log('column1checked ' + column1checked);
const column2checked = items[1].isChecked;
console.log('column2checked ' + column2checked);
const column3checked = items[2].isChecked;
console.log('column3checked ' + column3checked);
const column4checked = items[3].isChecked;
console.log('column4checked ' + column4checked);
const column5checked = items[4].isChecked;
console.log('column5checked ' + column5checked);
const column6checked = items[5].isChecked;
console.log('column6checked ' + column6checked);
const column7checked = items[6].isChecked;
console.log('column7checked ' + column7checked);
const column8checked = items[7].isChecked;
console.log('column8checked ' + column8checked);
}
render() {
return (
<div className='popup-page-custom' >
<div className='bottomBar'>
<ButtonComponent
text='Apply'
className='activeButton filterMargin'
width='100'
display='inline-block'
onClick={() => { this.props.applyFilter(this.state, false); }}
/>
<ButtonComponent
text='Clear Filter'
className='greyedButton clear-filter'
width='100'
display='block'
marginTop='60'
onClick={() => { this.props.applyFilter(this.state, true); }}
/>
</div>
<div>
<div className='data-points-text'>
<span> Columns </span>
</div>
<div className="App">
<ul>
{
this.state.items.map((item, i) => {
return (<div key={i} ><CheckBox handleCheckChildElement={this.handleCheckChildElement} {...item} /></div>);
})
}
</ul>
</div>
</div>
</div>
);
}
}
CustomizedView.propTypes = {
applyFilter: PropTypes.func.isRequired
};
CustomizedView.defaultProps = {
};
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CustomizedView);
And ultimately this is my checkbox page
import React from 'react';
import PropTypes from 'prop-types';
export const CheckBox = (props) => {
// super(props);
return (
<li>
<input key={props.id} onClick={props.handleCheckChildElement} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
);
};
CheckBox.propTypes = {
id: PropTypes.string,
handleCheckChildElement: PropTypes.func,
isChecked: PropTypes.bool,
value: PropTypes.string,
};
CheckBox.defaultProps = {
id: '',
handleCheckChildElement: null,
isChecked: null,
value: '',
};
export default CheckBox;
This is a very basic (ugly) style of my dashboard page and popup page
This is the error I am getting on Chrome when unchecking the checkboxes
Edit 1 - As per Alireza Yadegari's suggestion, I made a 1 line change. But I am still getting 2 errors.
Edit 2 - As per Alireza Yadegari's suggestion, I applied console.
you have to use this piece of code in your constructor
this.handleCheckChildElement = this.handleCheckChildElement.bind(this)
let { items } = { ...this.state };
this is wrong ....
firstly you destructuring array to object then saying give me items prop from given object... of course this is wrong
const { items} = this.state;
takes items prop from the state
and finally.... implement your task with foreach is bad idea...
CheckBox.defaultProps = {
id: '',
handleCheckChildElement: null,
isChecked: null, value: '',
};
i don't understand what it does. you know?
I think your project is a sample and no need for further examples.
I just say about your mistakes.
first, when you are using methods it is good to use 'bind(this)' to show react where is the method belongs.
secondly, when you are using state, react just allows you to change it in the constructor and wherever you want to change it you have to use 'setState' method (you can read the reason for this in react documentation).
finally, if you have an array in your state you have to get an array in some temp object change the temp object and then apply changes with 'setState' method. if you have more question please feel free to ask.
I'm using ReactJs + flux. In my store I have a method - getCurrentEvents(key).It recieves key and creates array of objects correctly. After that it emitts change.
I have component-list, which has handler. This handler answers to store change and calls method displayEvents(). This method updates component's state. This conception works but not correctly. When I call getCurrentEvents(key) it begins updating EventList-component and doesn't stop, as a result tab in browser freezes.As I understand I got something like limitless cycle of updates, I think that something wrong is in component's methods, but I can't understand where is the mistake. How to fix this bug?
store code:
class EventStore extends EventEmitter {
constructor() {
super();
this.events = [{
id: 1,
title: 'title 1',
date: '2017-04-11 09:14:01'
}, {
id: 2,
title: 'title 2',
date: '2017-04-11 09:24:01'
}, {
id: 3,
title: 'title 3',
date: '2017-04-12 09:14:01'
}, {
id: 4,
title: 'title 4',
date: '2017-11-12 19:14:01'
}, {
id: 5,
title: 'title 5',
date: '2017-06-13 19:00:01'
}
];
}
getCurrentEvents(key) {
var currentEvents = [];
for (event in this.events){
if (this.events[event].date.includes(key)) {
currentEvents.push(this.events[event]);
}
}
return currentEvents;
this.emit("change");
}
createEvent(new_event) {
this.events.push ({
title: new_event.title,
date: new_event.date
})
this.emit("change");
}
getAll() {
return this.events
}
handleActions(action) {
switch(action.type) {
case 'CREATE_EVENT' : {
this.createEvent(action.new_event);
}
case 'DISPLAY_CURRENT_EVENTS' : {
this.getCurrentEvents(action.key);
}
}
}
}
const eventStore = new EventStore;
dispatcher.register(eventStore.handleActions.bind(eventStore))
export default eventStore;
EventList component code:
export default class EventList extends React.Component {
constructor () {
super();
this.state = {
events: EventStore.getCurrentEvents()
};
this.displayEvents = this.displayEvents.bind(this);
}
componentWillMount() {
EventStore.on("change", this.displayEvents)
}
displayEvents() {
this.setState ({
events: EventStore.getCurrentEvents()
})
}
render() {
console.log('form events LIST', this.state)
const events = this.state.events ;
var EventsList = [];
for (event in events) {
EventsList.push(
<li key={events[event].id} id={events[event].id}>
{events[event].title} , {events[event].date}
</li>
)
}
return (
<div className="">
<h3> Events on this day </h3>
<ul className="event-list-wrapper">
{EventsList}
</ul>
</div>
);
}
}
Looks like it's because the getCurrentEvents(key) function is emitting a 'change' which is triggering displayEvents...which calls getCurrentEvents(key)...which emits a 'change' event which calls displayEvents...