Reputation: 383
I have a React DOM that looks like this:
In MarketDepthWindow
's state i have a isBackgroundEnabled
which i want to pass as a props to the AskOrdersTab
. But it passes only the initial props and not updating. It should update on MarketDepthHeaderComponent
's button click.
I assume that the problem is that i pass it as a prop to TabsContainer
.
I tried changing React.Component
to React.PureComponent
, using shouldComponentUpdate()
, adding React.cloneElement
and passing props through the TabsContainer
but nothing works for me.
Maybe I made some kind of architectural mistake? Maybe i should use refs in some way?
class AskOrdersTab extends React.Component {
render() {
return <div>bids</div>
}
}
class BidOrdersTab extends React.Component {
render() {
return <div>asks</div>
}
}
class MarketDepthHeaderComponent extends React.Component {
render() {
return <button class={this.props.isBackgroundEnabled ? 'active' : ''} onClick={this.props.handleBackgroundButtonClick}></button>
}
}
class TabsContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
tabs: props.tabs.tabs || [],
tabHeaderComponent: props.tabs.tabHeaderComponent || ''
}
}
renderComponents() {
let tabsComponents = [];
for (let tab of this.state.tabs.length) {
tabsComponents.push(
<div>{tab.component}</div>
)
}
return tabsComponents;
}
render() {
return (
<>
{this.state.tabHeaderComponent}
{this.renderComponents()}
</>
);
}
}
class MarketDepthWindow extends React.Component {
handleBackgroundButtonClick = (event) => {
this.setState((prevState) => ({
isBackgroundEnabled: !prevState.isBackgroundEnabled
}))
}
constructor(props) {
super(props)
this.state = {
isBackgroundEnabled: true,
handleBackgroundButtonClick: this.handleBackgroundButtonClick
}
}
render() {
let tabsProps = {
tabs: [
{ title: 'Bid', component: <BidOrdersTab {...this.state} /> },
{ title: 'Ask', component: <AskOrdersTab {...this.state} /> }
],
tabHeaderComponent: <MarketDepthHeaderComponent {...this.state} />
}
return <TabsContainer tabs={tabsProps} isBackgroundEnabled={this.state.isBackgroundEnabled} />
}
}
ReactDOM.render(
<MarketDepthWindow />,
document.getElementById("market-depth-window")
);
Upvotes: 0
Views: 1080
Reputation: 1568
The state within TabsContainer is redundant, you should avoid storing state in each component as it cuts the update chain, Try using props instead.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class AskOrdersTab extends React.Component {
render() {
return <div>bids</div>;
}
}
class BidOrdersTab extends React.Component {
render() {
return <div>asks</div>;
}
}
class MarketDepthHeaderComponent extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>
{this.props.isBackgroundEnabled ? "enabled" : "disabled"}
</button>
);
}
}
class TabsContainer extends React.Component {
renderComponents() {
let tabsComponents = [];
for (let tab of this.props.tabs.tabs) {
tabsComponents.push(<div key={tab.title}>{tab.component}</div>);
}
return tabsComponents;
}
render() {
return (
<>
{this.props.tabs.tabHeaderComponent}
{this.renderComponents()}
</>
);
}
}
class MarketDepthWindow extends React.Component {
constructor(props) {
super(props);
this.state = {
isBackgroundEnabled: true
};
}
handleBackgroundButtonClick = () => {
this.setState({
isBackgroundEnabled: !this.state.isBackgroundEnabled
});
};
render() {
let tabsProps = {
tabs: [
{ title: "Bid", component: <BidOrdersTab {...this.state} /> },
{ title: "Ask", component: <AskOrdersTab {...this.state} /> }
],
tabHeaderComponent: (
<MarketDepthHeaderComponent
onClick={this.handleBackgroundButtonClick}
isBackgroundEnabled={this.state.isBackgroundEnabled}
/>
)
};
return (
<TabsContainer
tabs={tabsProps}
isBackgroundEnabled={this.state.isBackgroundEnabled}
/>
);
}
}
const rootElement = document.getElementById("market-depth-window");
ReactDOM.render(<MarketDepthWindow />, rootElement);
This is your demo, you can check the fix and make sure it works. Good Luck
Upvotes: 2
Reputation: 845
The simple answer to your question is that your storing AskOrdersTab
in the state of TabsContainer
, and in your case you want to access it via props.
Explanation: What this has caused is whenever your component updates, which it is doing, TabsContainer
will still render the original AskOrdersTab
(this is due to the AskOrdersTab
in state and the current version with updated props being the same object). There are ofcourse workarounds for this sort of behaviour using lifecycle hooks, namely getDerivedStateFromProps. But in this case I wouldn't advise that.
TL;DR replace your TabsContainer
with this one and it should work.
class TabsContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
tabHeaderComponent: props.tabs.tabHeaderComponent || '',
};
}
renderComponents() {
let tabsComponents = [];
for (let tab of this.props.tabs.tabs) {
tabsComponents.push(<div>{tab.component}</div>);
}
return tabsComponents;
}
render() {
return (
<>
{this.state.tabHeaderComponent}
{this.renderComponents()}
</>
);
}
}
Finally, I think you're storing too much in state. Try importing components, or take a look at two common react patterns:
Upvotes: 1