0

I have a react app with components in the following structure:

<App>
    <Toolbar this.state={sectionInFocus: someSectionID }/>
    <Chart>
       <Section> <input value=someSectionID /> <Section />
       <Section> <input value=someSectionID  /> <Section />
       <Section> <input value=someSectionID  /> <Section />
       ...more sections...
    </Chart>
</App>

I wish to adopt a functionality where the state of the toolbar is set when the input buttons in the sections are focused on (onFocus).

How do I set the state of toolbar to the id of the last selected section? Do I set the state in each section, passing back 2 levels up then to toolbar?

I have tried to at least set state in the root component to the focused sectionID, but now I am getting another error message (maximum call stack reached), like so:

App.js:

import React, { Component } from 'react';
import Chart from './chart';

export default class App extends Component {

  constructor(props){
    super(props)
    this.state = {
      sectionInFocus: ''
    }
  }

  componentDidUpdate(){
    console.log(this.state); // maximum call stack size exceeded
  }

  chartUpdate(obj){
    const { sectionInFocus } = obj;
    console.log(sectionInFocus);
    this.setState({ sectionInFocus });
  }

  render() {
    return (
    <div>
      <Chart chart={{"chartData":["abc","def","ghi"]}} onUpdate={this.chartUpdate.bind(this)}/>
    </div>
    )
  }

};

Chart.js

import React, { Component } from 'react';
import Section from './section';

export default class Chart extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sectionInFocus: ""
    }
  }

  componentDidUpdate(){
    console.log(this.state.sectionInFocus);
    this.props.onUpdate(this.state);
  }

  sectionUpdate(sectionID) {
    this.setState({sectionInFocus: sectionID});
  }

  renderSections(chartData) {
    return chartData.map((section) => {
      return (
        <Section
          key = {section}
          sectionID = {section}
          onUpdate={this.sectionUpdate.bind(this)}
        />
      )
    })
  }

  render(){
    let chartData = this.props.chart.chartData;
    return (
      <div>
        {this.renderSections(chartData)}
      </div>
    )
  }
}

Section.js

import React, { Component } from 'react';

export default class Section extends Component {
  updateParentState(){
    this.props.onUpdate(this.props.sectionID)
  }
  render(){
    return (
      <div>
        <input
          value={this.props.sectionID}
          onFocus={this.updateParentState.bind(this)}
        />
      </div>
    )
  }
}

What am I doing wrong that I am getting the maximum call stack error message? Is there an easier way to go about handling this sort of functionality, or is this kind of the react way of doing things? Should I be looking into redux just to manage this efficiently?

Any help provided is greatly appreciated!

Poh Zi How
  • 1,489
  • 3
  • 16
  • 38
  • if you make this as a working snipped, it would be much better – uladzimir Feb 13 '17 at 15:17
  • `chart.js` => `this.props.onUpdate(this.state)` you're doing an update after update, then setState of parent component that leads to another check and another update, especially because you're using `this.chartUpdate.bind(this)` which is new every time. Just a suggestion :) – uladzimir Feb 13 '17 at 15:20
  • hi @havenchyk, I have modified my question to include a working snippet – Poh Zi How Feb 13 '17 at 16:32
  • @havenchyk regarding the update after updates issue, I see where you are coming from. That seems to be in the right direction, though I'm not sure what would be a proper workaround for that – Poh Zi How Feb 13 '17 at 16:34
  • I meant to create a snippet that works right from SO (or jsfiddle), but anyway, thanks – uladzimir Feb 13 '17 at 19:01

1 Answers1

1

Do I set the state in each section, passing back 2 levels up then to toolbar?

No

How do I set the state of toolbar to the id of the last selected section?

You should only have one source of truth for your sectionInFocus data. This means no matter how many parents your component has you should only set state in your highest root component ( a maximum level where your data needs to be used). In your case, it is App component (better to have state management like Redux, but this is out of scope for this question now).

So you should do something like this:

App.js:

import React, { Component } from 'react';
import Chart from './chart';

export default class App extends Component {

  constructor(props){
    super(props)
    this.state = {
      sectionInFocus: ''
    }
    this.chartUpdate = this.chartUpdate.bind(this);
  }

  componentDidUpdate(){
    console.log(this.state); // maximum call stack size exceeded
  }

  chartUpdate(obj){
    const { sectionInFocus } = obj;
    console.log(sectionInFocus);
    this.setState({ sectionInFocus });
  }

  render() {
    return (
    <div>
      <Chart chart={{"chartData":["abc","def","ghi"]}} onUpdate={this.chartUpdate}/>
    </div>
    )
  }

};

Chart.js

import React, { Component } from 'react';
import Section from './section';

export default class Chart extends Component {

  renderSections(chartData) {
    return chartData.map((section) => {
      return (
        <Section
          key = {section}
          sectionID = {section}
          onUpdate={this.props.sectionUpdate}
        />
      )
    })
  }

  render(){
    let chartData = this.props.chart.chartData;
    return (
      <div>
        {this.renderSections(chartData)}
      </div>
    )
  }
}

Section.js

import React, { Component } from 'react';

export default class Section extends Component {
  render(){
    return (
      <div>
        <input
          value={this.props.sectionID}
          onFocus={() => this.props.onUpdate(this.props.sectionID)}
        />
      </div>
    )
  }
}
Shota
  • 6,910
  • 9
  • 37
  • 67