如何避免在 React 的 shouldComponentUpdate() 中使用 setState?
How to avoid using setState in shouldComponentUpdate() in React?
我为论坛视图创建了一个 React 组件。
几个不同论坛的顶部有一些标签。
当我们点击标签时,state forumID
用于检查点击了哪个标签以及应该显示哪个论坛。
state discussions
用于存放论坛的内容。
state isLoading
用于标记请求何时发送到服务器。
import React from 'react'
// nodejs library that concatenates classes
import classNames from 'classnames'
// @material-ui/core components
import withStyles from '@material-ui/core/styles/withStyles'
import Tooltip from '@material-ui/core/Tooltip'
import Tab from '@material-ui/core/Tab'
import Tabs from '@material-ui/core/Tabs'
import CircularProgress from '@material-ui/core/CircularProgress'
// @material-ui/icons
import Reply from '@material-ui/icons/Reply'
// core components
import GridContainer from 'components/Grid/GridContainer.jsx'
import GridItem from 'components/Grid/GridItem.jsx'
import Button from 'components/CustomButtons/Button.jsx'
import Media from 'components/Media/Media.jsx'
import { getDiscussionsByForumID } from 'util/APIUtils'
import profile4 from 'assets/img/faces/card-profile4-square.jpg'
import sectionPillsStyle from 'assets/jss/material-kit-pro-react/views/blogPostsSections/sectionPillsStyle.jsx'
class SectionPills extends React.Component {
constructor (props) {
super(props)
this.state = {
discussions: [],
isLoading: false,
active: 0,
forumID: 1
}
this.changeForum = this.changeForum.bind(this);
}
handleChange = (event, active) => {
this.setState({ active });
};
changeForum(fid) {
this.setState({ forumID: fid });
}
loadDiscussionList () {
let promise
promise = getDiscussionsByForumID(this.state.forumID)
if (!promise) {
return
}
this.setState({
isLoading: true
})
promise
.then(response => {
const discussions = this.state.discussions.slice()
this.setState({
discussions: discussions.concat(response),
isLoading: false
})
}).catch(error => {
this.setState({
isLoading: false
})
})
}
componentDidMount () {
this.loadDiscussionList()
}
shouldComponentUpdate (nextState) {
if (nextState.forumID != this.state.forumID) {
this.loadDiscussionList()
}
}
render () {
const { classes } = this.props
const flexContainerClasses = classNames({
[classes.flexContainer]: true,
[classes.horizontalDisplay]: false
})
const pillsClasses = classNames({
[classes.pills]: true,
[classes.horizontalPills]: false
})
const tabButtons = (
<Tabs
classes={{
root: classes.root,
fixed: classes.fixed,
flexContainer: flexContainerClasses,
indicator: classes.displayNone
}}
value={this.state.active}
onChange={this.handleChange}
centered={true}
>
<Tab
key={1}
label='YeePlus'
classes={{
root: pillsClasses,
labelContainer: classes.labelContainer,
label: classes.label,
selected: classes.primary
}}
onClick={() => this.changeForum(1)}
/>
<Tab
key={2}
label='Yeelight'
classes={{
root: pillsClasses,
labelContainer: classes.labelContainer,
label: classes.label,
selected: classes.primary
}}
onClick={() => this.changeForum(2)}
/>
<Tab
key={3}
label='Feedback'
classes={{
root: pillsClasses,
labelContainer: classes.labelContainer,
label: classes.label,
selected: classes.primary
}}
onClick={() => this.changeForum(3)}
/>
</Tabs>
)
const discussionList = []
this.state.discussions.forEach((discussion, discussionIndex) => {
discussionList.push(
<Media
key={discussion.id}
avatar={profile4}
title={
<span>
{discussion.title} <small>· 7 minutes ago</small>
</span>
}
body={
<p className={classes.color555}>
{discussion.title}
</p>
}
footer={
<div>
<Tooltip
id='tooltip-tina'
title='Reply to discussion'
placement='top'
classes={{ tooltip: classes.tooltip }}
>
<Button
color='primary'
simple
className={classes.footerButtons}
>
<Reply className={classes.footerIcons} /> Reply
</Button>
</Tooltip>
</div>
}
/>)
})
const ColorCircularProgress = withStyles({
root: {
color: '#9c27b0'
}
})(CircularProgress)
return (
<div className={classes.section}>
<GridContainer justify='center'>
<GridItem xs={12} sm={10} md={8}>
{tabButtons}
<div className={classes.tabSpace} />
{discussionList}
<div className={classes.textCenter} >
{
this.state.isLoading
? <ColorCircularProgress /> : null
}
</div>
</GridItem>
</GridContainer>
</div>
)
}
}
export default withStyles(sectionPillsStyle)(SectionPills)
错误信息:
Error: 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.
50 | return
51 | }
52 |
> 53 | this.setState({
| ^ 54 | isLoading: true
55 | })
56 |
我想保留 state isLoading
,但找不到同时保留它们的解决方案。
您无法在 componentShouldUpdate
中设置状态。相反,在发布更新论坛 ID 的 setState
时,还会启动为该新论坛加载讨论的过程。由于这是一个异步过程,因此当它完成时,您需要检查用户是否同时单击了另一个论坛。大致:
constructor(props) {
// ...
this.loadId = 0;
// ...
}
changeForum(fid) {
this.setState({ forumID: fid, discussions: [] }); // *** Note clearing discussions!
this.loadDiscussionList(fid);
}
loadDiscussionList(fid = this.state.forumID) {
// *** To handle out-of-sequence completions and/or the user changing forums
// while loading is happening, use a unique load identifier (more below)
let loadId = ++this.loadId;
let promise = getDiscussionsByForumID(fid);
if (!promise) { // ??? Normally a method returning a promise always does so
return;
}
this.setState({
isLoading: true
});
promise
.then(response => {
const discussions = this.state.discussions.slice();
this.setState(({forumID}) => {
// Note the check, *within* the `setState` callback:
// 1. We're still on the same forum, and
// 2. There hasn't been a more recent call to `loadDiscussionList`
if (forumID === fid && this.loadId === loadId) {
return {
discussions: discussions.concat(response),
loading: false
};
}
});
})
.catch(error => {
// *** Usually you report an error
// *** Same check as above
this.setState(({forumID}) => {
if (forumID === fid && this.loadId === loadId) {
this.setState({isLoading: false});
}
});
});
}
您还需要调整(或移除)您的 shouldComponentUpdate
。它至少需要检查 forumID
、isLoading
和 discussions
,可能还有其他的。
我为论坛视图创建了一个 React 组件。
几个不同论坛的顶部有一些标签。
当我们点击标签时,state forumID
用于检查点击了哪个标签以及应该显示哪个论坛。
state discussions
用于存放论坛的内容。
state isLoading
用于标记请求何时发送到服务器。
import React from 'react'
// nodejs library that concatenates classes
import classNames from 'classnames'
// @material-ui/core components
import withStyles from '@material-ui/core/styles/withStyles'
import Tooltip from '@material-ui/core/Tooltip'
import Tab from '@material-ui/core/Tab'
import Tabs from '@material-ui/core/Tabs'
import CircularProgress from '@material-ui/core/CircularProgress'
// @material-ui/icons
import Reply from '@material-ui/icons/Reply'
// core components
import GridContainer from 'components/Grid/GridContainer.jsx'
import GridItem from 'components/Grid/GridItem.jsx'
import Button from 'components/CustomButtons/Button.jsx'
import Media from 'components/Media/Media.jsx'
import { getDiscussionsByForumID } from 'util/APIUtils'
import profile4 from 'assets/img/faces/card-profile4-square.jpg'
import sectionPillsStyle from 'assets/jss/material-kit-pro-react/views/blogPostsSections/sectionPillsStyle.jsx'
class SectionPills extends React.Component {
constructor (props) {
super(props)
this.state = {
discussions: [],
isLoading: false,
active: 0,
forumID: 1
}
this.changeForum = this.changeForum.bind(this);
}
handleChange = (event, active) => {
this.setState({ active });
};
changeForum(fid) {
this.setState({ forumID: fid });
}
loadDiscussionList () {
let promise
promise = getDiscussionsByForumID(this.state.forumID)
if (!promise) {
return
}
this.setState({
isLoading: true
})
promise
.then(response => {
const discussions = this.state.discussions.slice()
this.setState({
discussions: discussions.concat(response),
isLoading: false
})
}).catch(error => {
this.setState({
isLoading: false
})
})
}
componentDidMount () {
this.loadDiscussionList()
}
shouldComponentUpdate (nextState) {
if (nextState.forumID != this.state.forumID) {
this.loadDiscussionList()
}
}
render () {
const { classes } = this.props
const flexContainerClasses = classNames({
[classes.flexContainer]: true,
[classes.horizontalDisplay]: false
})
const pillsClasses = classNames({
[classes.pills]: true,
[classes.horizontalPills]: false
})
const tabButtons = (
<Tabs
classes={{
root: classes.root,
fixed: classes.fixed,
flexContainer: flexContainerClasses,
indicator: classes.displayNone
}}
value={this.state.active}
onChange={this.handleChange}
centered={true}
>
<Tab
key={1}
label='YeePlus'
classes={{
root: pillsClasses,
labelContainer: classes.labelContainer,
label: classes.label,
selected: classes.primary
}}
onClick={() => this.changeForum(1)}
/>
<Tab
key={2}
label='Yeelight'
classes={{
root: pillsClasses,
labelContainer: classes.labelContainer,
label: classes.label,
selected: classes.primary
}}
onClick={() => this.changeForum(2)}
/>
<Tab
key={3}
label='Feedback'
classes={{
root: pillsClasses,
labelContainer: classes.labelContainer,
label: classes.label,
selected: classes.primary
}}
onClick={() => this.changeForum(3)}
/>
</Tabs>
)
const discussionList = []
this.state.discussions.forEach((discussion, discussionIndex) => {
discussionList.push(
<Media
key={discussion.id}
avatar={profile4}
title={
<span>
{discussion.title} <small>· 7 minutes ago</small>
</span>
}
body={
<p className={classes.color555}>
{discussion.title}
</p>
}
footer={
<div>
<Tooltip
id='tooltip-tina'
title='Reply to discussion'
placement='top'
classes={{ tooltip: classes.tooltip }}
>
<Button
color='primary'
simple
className={classes.footerButtons}
>
<Reply className={classes.footerIcons} /> Reply
</Button>
</Tooltip>
</div>
}
/>)
})
const ColorCircularProgress = withStyles({
root: {
color: '#9c27b0'
}
})(CircularProgress)
return (
<div className={classes.section}>
<GridContainer justify='center'>
<GridItem xs={12} sm={10} md={8}>
{tabButtons}
<div className={classes.tabSpace} />
{discussionList}
<div className={classes.textCenter} >
{
this.state.isLoading
? <ColorCircularProgress /> : null
}
</div>
</GridItem>
</GridContainer>
</div>
)
}
}
export default withStyles(sectionPillsStyle)(SectionPills)
错误信息:
Error: 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.
50 | return
51 | }
52 |
> 53 | this.setState({
| ^ 54 | isLoading: true
55 | })
56 |
我想保留 state isLoading
,但找不到同时保留它们的解决方案。
您无法在 componentShouldUpdate
中设置状态。相反,在发布更新论坛 ID 的 setState
时,还会启动为该新论坛加载讨论的过程。由于这是一个异步过程,因此当它完成时,您需要检查用户是否同时单击了另一个论坛。大致:
constructor(props) {
// ...
this.loadId = 0;
// ...
}
changeForum(fid) {
this.setState({ forumID: fid, discussions: [] }); // *** Note clearing discussions!
this.loadDiscussionList(fid);
}
loadDiscussionList(fid = this.state.forumID) {
// *** To handle out-of-sequence completions and/or the user changing forums
// while loading is happening, use a unique load identifier (more below)
let loadId = ++this.loadId;
let promise = getDiscussionsByForumID(fid);
if (!promise) { // ??? Normally a method returning a promise always does so
return;
}
this.setState({
isLoading: true
});
promise
.then(response => {
const discussions = this.state.discussions.slice();
this.setState(({forumID}) => {
// Note the check, *within* the `setState` callback:
// 1. We're still on the same forum, and
// 2. There hasn't been a more recent call to `loadDiscussionList`
if (forumID === fid && this.loadId === loadId) {
return {
discussions: discussions.concat(response),
loading: false
};
}
});
})
.catch(error => {
// *** Usually you report an error
// *** Same check as above
this.setState(({forumID}) => {
if (forumID === fid && this.loadId === loadId) {
this.setState({isLoading: false});
}
});
});
}
您还需要调整(或移除)您的 shouldComponentUpdate
。它至少需要检查 forumID
、isLoading
和 discussions
,可能还有其他的。