Reputation: 2189
I'm having an issue passing down handlers through my React components.
I've tried following the instructions in the Lifting State Up section of the React docs.
The idea is to have a Page with tabbed navigation, and each tab would render the display of some subpage. I have a component Page.js
for my overall page, and that's where I'm storing the activeTab state and where I think I should define the function that handles state change. I then pass down the activeTab state and the handler as props to the TabMenu.js
component, which in turn passes it down to the TabItem.js
components.
The files:
Page.js
import React, { Component } from 'react';
import TabMenu from './TabMenu';
import FooPage from './FooPage';
import BarPage from './BarPage';
class Page extends Component {
constructor(props) {
super(props);
this.state = {
activeTab: 'foo'
};
this.setActiveTab = this.setActiveTab.bind(this);
}
getVisiblePage() {
switch(this.state.activeTab) {
case 'bar':
return (
<FooPage />
);
case 'foo':
default:
return (
<BarPage />
);
}
}
setActiveTab(e, tab) {
this.setState({
activeTab: tab
});
}
render() {
var visiblePage = this.getVisiblePage();
return (
<section>
<TabMenu
activeTab={ this.state.activeTab }
changeTabHandler={ this.setActiveTab }
/>
{ visiblePage }
</section>
);
}
}
export default Page;
TabMenu.js:
import React, { PropTypes } from 'react';
import TabItem from './TabItem';
const TabMenu = ({ activeTab, changeTabHandler }) => {
const tabs = [
{
key: 'foo',
text: 'Foo Page',
},
{
key: 'bar',
text: 'Bar Page'
},
];
const tabItems = tabs.map((item) => (
<TabItem
key={ item.key }
item={ item }
isActive={ item.key === activeTab }
changeTabHandler={ changeTabHandler }
/>
));
return (
<nav id="TabMenu">
<ul className="tab-items">
{ tabItems }
</ul>
</nav>
);
};
TabMenu.displayName = 'TabMenu';
TabMenu.propTypes = {
activeTab: PropTypes.string,
changeTabHandler: PropTypes.func,
};
export default TabMenu;
TabItem.js
import React, { PropTypes } from 'react';
const TabItem = ({ item, changeTabHandler }) => {
return (
<li onClick={ changeTabHandler(item.key) }>
{ item.text }
</li>
);
};
TabItem.displayName = 'TabItem';
TabItem.propTypes = {
item: PropTypes.object,
changeTabHandler: PropTypes.func,
};
export default TabItem;
The end results is that my console overflows with 1000s of copies of the following error:
warning.js:36 Warning: setState(...): Cannot update during an existing state transition (such as within
render
or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved tocomponentWillMount
.
What am I doing wrong?
Upvotes: 1
Views: 1310
Reputation: 9681
That infinite loop is because you have something inside the parent component's render function, which invoke setState
or trigger some update to another component which affects the state of the origin or parent component which then will call render again.
in your case, it's because in TabItem.js,
<li onClick={ changeTabHandler(item.key) }>
{ item.text }
</li>
actually invoke changeTabHandler
immediately which will do setState in Page, and then TabItem will render and call changeTabHandler
again
change it to
<li onClick={() => changeTabHandler(item.key) }>
{ item.text }
</li>
Upvotes: 4
Reputation: 5927
Your changeTabHandler()
is getting invoked immediately, once for each <li>
you are rendering. Change this:
<li onClick={ changeTabHandler(item.key) }>
to this:
<li onClick={ () => changeTabHandler(item.key) }>
Upvotes: 1