jastew
jastew

Reputation: 323

React Component State issue with duplicate components

I've recently started learning React and I'm a little bit confused.

Please see the following codepen or the code snippet below.

class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      sessions: [],
      sessionSelected: null
    }
    this.addSession = this.addSession.bind(this);
    this.switchSession = this.switchSession.bind(this);
  }

  addSession() {
    let sessions = this.state.sessions;
    let id = (sessions.length)
    let title = "Session " + id;
    let session = <Session title={title} index={id + 1} />;
    sessions.push(session);
    this.setState({
      sessions: sessions,
      sessionSelected: id
    });
  }

  switchSession(id) {
    this.setState({
      sessionSelected: id
    });
  }

  render() {
    return (
      <div>
        <button onClick={this.addSession} >+ Add Session</button>
        <div>{this.state.sessions[this.state.sessionSelected]}</div>
        <div className="switchers">
          {this.state.sessions.map((x, i) => {
            return <SessionSwitcher index={i + 1} onClick={() => { this.switchSession(i) }} />;
          })}
        </div>
      </div>
    );
  }
}

class Session extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
    this.startTimer = this.startTimer.bind(this);
    this.count = this.count.bind(this);
  }
  startTimer() {
    setInterval(this.count, 1000);
  }
  count() {
    this.setState({
      count: this.state.count + 1
    });
  }
  render() {
    return (
      <div>
        <h1>{this.props.title}</h1>
        <p>{this.state.count}</p>
        <button onClick={this.startTimer}>Start Timer</button>
      </div>
    )
  }
}

class SessionSwitcher extends React.Component {
  render() {
    return (
      <div className="switcher" onClick={this.props.onClick}>
        <span>{this.props.index}</span>
      </div>
    )
  }
}

ReactDOM.render(
  <App />,
  document.querySelector('#app')
);

I want to be able to trigger multiple timers within multiple components.

For some reason when I click the start timer in one component, it triggers it for the other components too.

Can someone explain to me what I am doing wrong?

Upvotes: 2

Views: 8426

Answers (1)

glortho
glortho

Reputation: 13200

Here are a couple of things you could change to get a more predictable experience:

  1. Rather than storing JSX in state, which is a complex object, store just the id, and render JSX according to that id.

  2. Try not to mutate state in place (as with pushing onto an array that's already in state).

  3. If you want to have persistent timers across components, regardless of which ones are currently selected, be sure not to unmount them when they're deselected.

Here are the relevant updated parts of your <App/> component (note that these are more quick fixes than best practices):

  addSession() {
    const id = this.state.sessions.length;
    this.setState( state => ({
      // treat your state as immutable
      sessions: state.sessions.concat( id ),
      sessionSelected: id
    }));
  }

  // don't unmount when switching components, just hide
  render() {
    return (
      <div>
        <button onClick={this.addSession} >+ Add Session</button>
        <div>
          { this.state.sessions.map( session => 
            <div style={{ display: session === this.state.sessionSelected ? 'block' : 'none' }}>
              <Session title={"Session " + session} />
            </div>
          )}
        </div>
        <div className="switchers">
          {this.state.sessions.map((x, i) => {
            return <SessionSwitcher index={i + 1} onClick={() => { this.switchSession(i) }} />;
          })}
        </div>
      </div>
    );
  }

Try it here: https://codepen.io/glortho/pen/ZrLMda?editors=0011

Upvotes: 3

Related Questions