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.
Related
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: ""
}
}
I am trying to set a state using enzyme method. After setting a state inside test case I was able to get it back again within the test case which proves that setState is working and I can see the expected output in console. However, inside component I was not able to get state which was set by enzyme because of this my is failing and I am not getting desire view back from component, here is screenshot. You can see there is not data grid table rendered despite having rows data.
table.js
import React, { Component } from 'react';
import styled from 'styled-component';
import ReactDataGrid from 'react-data-grid';
import { Filters } from 'react-data-grid-addons';
import PropTypes from 'prop-types';
export class Table extends Component {
constructor(props) {
super(props);
const {
NumericFilter,
AutoCompleteFilter,
} = Filters;
this.columns = [
{ key: "id", name: "ID", editable: true },
{ key: "title", name: "Title", editable: true },
{ key: "complete", name: "Complete", editable: true }
];
this.state = {
rows: [],
};
}
renderRow = (i) => {
const { rows } = this.state;
return rows[i];
}
render() {
const { className } = this.props;
const { rows } = this.state;
console.log('rows Length', rows.length) // always 0
return (
<div className={className}>
{rows.length
? (
<ReactDataGrid
rowHeight={50}
columns={this.columns}
rowGetter={this.renderRow}
rowsCount={rows.length}
/>
)
: <span id="no-product-message">No Items to be Shown</span>
}
</div>
);
}
}
Table.propTypes = {
className: PropTypes.string,
};
Table.defaultProps = {
className: '',
};
export default styled(Table)`
.react-grid-HeaderCell{
white-space: normal !important;
}
.react-grid-Cell__value{
display: flex;
align-items: center;
}
`;
mountWithTheme
export const mountWithTheme = (children, options) => (
mount(<ThemeProvider theme={theme}>{children}</ThemeProvider>, options)
);
test case
it('should render table if product data is available', () => {
const wrapper = mountWithTheme(<Table />).find(Table);
const instance = wrapper.instance();
jest.spyOn(instance, 'renderRow');
instance.setState({
rows: [{ id: 0, title: "Task 1", complete: 20 }],
});
console.log(instance.state.rows) // [{ id: 0, title: "Task 1", complete: 20 }]
expect(wrapper.find('ReactDataGrid').exists()).toBe(true);
expect(instance.renderRow).toHaveBeenCalledTimes(1);
});
I am implementing the expandable row feature on an ant design table (Expandable Row), and it works perfectly fine as stated on the ant design site. But I would like to expand the functionality of the table to include collapsing of the rows when the user clicks on the buttons to the lower right of the table that allow pagination. This is a fairly straightforward question so I won't clutter it by posting code. Any help or links would be greatly appreciated.
EDIT Code snippet
import * as React from 'react';
import { Tooltip, Table } from 'antd';
import * as IAssignmentsResponse from '../../interfaces/QC/IAssignmentResponse';
import * as moment from 'moment';
const expandedRowRender = (rowData) => {
const columns = [
{ title: 'Row1', dataIndex: 'Row1DataIndex', key: '1'},
{ title: 'Row2', dataIndex: 'Row2DataIndex', key: '2'},
{ title: 'Row3', dataIndex: 'Row3DataIndex', key: '3'},
];
return <Table
columns={columns}
dataSource={rowData.DataArray}
pagination={false}>
</Table>
}
const bindRows = (row) => {
return row.Workitem.WorkflowRefID;
}
const columns = [
{
title: 'MasterRow1',
dataIndex: 'MasterRow1DataIndex',
key: '1',
render(value) { return value.WorkflowRefID; },
onFilter: (value, record) => record.Workitem.data1.indexOf(value) === 0,
sorter: (a, b) => a.Workitem.data1 - b.Workitem.data1
},
{
title: 'MasterRow2',
dataIndex: 'MasterRow1DataIndex',
key: '2',
render(value, record) { return <Tooltip title={record.data2} mouseEnterDelay={.5}>{value}</Tooltip> },
onFilter: (value, record) => record.data2.indexOf(value) === 0,
sorter: (a, b) => a.data2- b.data2
},
{
title: 'MasterRow3',
dataIndex: 'MasterRow1DataIndex',
key: '3',
render(value, record) { return <Tooltip title={record.data3} mouseEnterDelay={.5}>{value}</Tooltip> },
onFilter: (value, record) => record.data3.indexOf(value) === 0,
sorter: (a, b) => a.data3- b.data3
}
]
return <Table rowKey={record => bindRows(record)}
columns={columns}
dataSource={this.props.assignmentData.AssignmentsResponse.Assignment}
expandedRowRender={record => expandedRowRender(record)}
onExpand={this.onTableRowExpand}
/>
You can achieve this using the expandedRowKeys method of the Ant Design's Table component's API, which is the list of keys of already expanded rows. An empty list means all rows are collapsed.
You can capture pagination button clicks using the onChange method, which can be used to call a function that sets the state of your component:
class MyTable extends Component {
constructor(props) {
super(props);
this.state = { currentPage: 1, expandedRows: [] };
this.handleChange = this.handleChange.bind(this);
}
handleChange(pagination) {
// changes the page, collapses all rows
this.setState({ currentPage: pagination.current, expandedRows: [] });
}
handleRowExpand(record) {
// if a row is expanded, collapses it, otherwise expands it
this.setState(prevState =>
prevState.expandedRows.includes(record.key)
? {
expandedRows: prevState.expandedRows.filter(
key => key !== record.key
)
}
: { expandedRows: [...prevState.expandedRows, record.key] }
);
}
render() {
return (
<Table
dataSource={YOUR_DATA}
...OTHER_PROPS
pagination={{ current: this.state.currentPage }}
// pagination buttons clicked
onChange={this.handleChange}
// expand + icon clicked
onExpand={(expanded, record) => this.handleRowExpand(record)}
// tell the 'Table' component which rows are expanded
expandedRowKeys={this.state.expandedRows}
/>
);
}
}
I am rendering a table with ant design and it works fine, but there is a warning in the console:
Each record in table should have a unique key prop,or set rowKey
to an unique primary key
My code is as follows:
import React, { Component } from 'react';
import { Table} from 'antd';
import { adalApiFetch } from '../../adalConfig';
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
ClientId: row.ClientId,
ClientSecret: row.ClientSecret,
Id: row.Id,
SiteCollectionTestUrl: row.SiteCollectionTestUrl,
TenantDomainUrl: row.TenantDomainUrl
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'Client Id',
dataIndex: 'ClientId',
key: 'ClientId'
},
{
title: 'Site Collection TestUrl',
dataIndex: 'SiteCollectionTestUrl',
key: 'SiteCollectionTestUrl',
},
{
title: 'Tenant DomainUrl',
dataIndex: 'TenantDomainUrl',
key: 'TenantDomainUrl',
}
];
return (
<Table columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
Just add a unique key value in tag link this:
<Table
columns={columns}
dataSource={this.state.data}
rowKey="Id" /> // unique key
Hope this help
React renders lists using the key prop. It works so because react allows you to reduce the complexity of diffing algorithms and reduce the number of DOM mutations. You can read a bit more in react reconciliation docs: https://reactjs.org/docs/reconciliation.html
In your case, you added the keys to the columns, but not for rows. Add the key field to the data source. So your code could be the following:
import React, { Component } from 'react';
import { Table} from 'antd';
import { adalApiFetch } from '../../adalConfig';
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id, // I added this line
ClientId: row.ClientId,
ClientSecret: row.ClientSecret,
Id: row.Id,
SiteCollectionTestUrl: row.SiteCollectionTestUrl,
TenantDomainUrl: row.TenantDomainUrl
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'Client Id',
dataIndex: 'ClientId',
key: 'ClientId'
},
{
title: 'Site Collection TestUrl',
dataIndex: 'SiteCollectionTestUrl',
key: 'SiteCollectionTestUrl',
},
{
title: 'Tenant DomainUrl',
dataIndex: 'TenantDomainUrl',
key: 'TenantDomainUrl',
}
];
return (
<Table columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
React Table unique key / rowKey
Each record in table should have a unique key prop,or set rowKey to an unique primary key.
solution 1
each col has a unique key
// each column with unique key
import React from 'react';
import {
Table,
} from 'antd';
const leftTableColumns = [
{
title: 'Page / Modal',
dataIndex: 'pageModal',
key: 'pageModal',
},
{
title: 'Success Rate',
dataIndex: 'successRate',
key: 'successRate',
},
];
const LeftTable = (props) => {
const {
leftTableDatas,
} = props;
return (
<>
<Table
columns={leftTableColumns}
dataSource={leftTableDatas}
/>
</>
);
};
export {
LeftTable,
};
export default LeftTable;
solution 2
only need set rowkey on the table with the unique value
// table with rowkey
import React from 'react';
import {
Table,
} from 'antd';
const leftTableColumns = [
{
title: 'Page / Modal',
dataIndex: 'pageModal',
},
{
title: 'Success Rate',
dataIndex: 'successRate',
},
];
const LeftTable = (props) => {
const {
leftTableDatas,
} = props;
return (
<>
<Table
// shorthand rowKey
rowKey="id"
// rowKey={obj => obj.id}
columns={leftTableColumns}
dataSource={leftTableDatas}
/>
</>
);
};
export {
LeftTable,
};
export default LeftTable;
ref
https://ant.design/components/table/
only you need set rowkey on the table with the unique value.
<Table
dataSource={[finance]}
columns={columns}
rowKey={record => record.id}
/>
Because you are not adding key to dataSource array, add a key in that also.
Like this:
const results= responseJson.map(row => ({
key: row.ClientId, // here
ClientId: row.ClientId,
ClientSecret: row.ClientSecret,
Id: row.Id,
SiteCollectionTestUrl: row.SiteCollectionTestUrl,
TenantDomainUrl: row.TenantDomainUrl
}))
Or you can use any unique value of dataSource array as key by using property rowKey, like this:
<Table
columns={columns}
dataSource={this.state.data}
rowKey="Id" /> // any unique value
Doc Reference.
for me worked this solution
rowKey="{record => record.id}"
or rowKey="id"
but in your collection have to exist the id column
I had your problem and this method answered
<Table
columns={columns}
dataSource={data}
rowKey="name"
/>
I have the same issue but solved with this, Hope this help :)
just add rowKey='id'
I use rowKey = 'id' because 'id' is unique data in variable dataSource, but you can use another unique value from variable dataSource. (with unique value)
import React from 'react'
import { Table } from 'antd'
const committeInformation = () => {
const columns = [
{
key: '0',
title: 'id',
dataIndex: 'id',
},
{
key: '1',
title: 'name',
dataIndex: 'name',
},
{
key: '2',
title: 'email',
dataIndex: 'email'
}
]
const dataSource = [
{
id: '1',
name: 'user 1',
email: 'coco#gmail.com',
},
{
id: '2',
name: 'user 2',
email: 'coco#gmail.com'
}
]
return (
<Table
rowKey='id' //this from variable dataSource
columns={columns}
dataSource={dataSource}
/>
)
}
export default committeInformation
Fast hack
I do assign random math numbers changed to string on each key.?
This is to say my columns become
const columns = [
{
title: 'Client Id',
dataIndex: 'ClientId',
key: () => Math.random().toString(),
},
{
title: 'Site Collection TestUrl',
dataIndex: 'SiteCollectionTestUrl',
key: () => Math.random().toString(),
},
{
title: 'Tenant DomainUrl',
dataIndex: 'TenantDomainUrl',
key: () => Math.random().toString(),
}
];
Hope this helps anybody visiting from the future..........
If your data has no logical key and you only need the key for presentation purposes, you could intercept the data and write a key in using a random number.
export const Table = ({
data,
}: {
data: ITableData[] | undefined;
}) => {
....
if (data) {
data.map((record) => {
return (record.id = Math.floor(Math.random() * 1000000));
});
and then assign the rowkey to the table
<Table dataSource={data} rowKey="id">
in Vue 3:
<a-table
...
:columns="yourColumns"
:data-source="yourData"
// the solution:
:row-key="record => record.id"
// or
:rowKey="record => record.id"
>
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.