0
votes

I'm trying add some very simple click navigation to my React components. App is a component that can render either Component1 or Component2. Component1 contains a link that when clicked should tell App to render Component2.

The problem is when the anchor is clicked, Component2 is displayed briefly and then Component1 is rendered again instead. For some unexplained reason, App.render() is getting called twice: once when state.view is changed and once again after Component2.render() has been called. It appears that this second time App.render() is called it has an old state so it's rendering Component1 instead.

Can anyone explain why App.render() is being called a second time and why Component1 is being rendered after Component2 has already been rendered?

I know I can use react-router to help me here but it seems like overkill for what I'm doing - AND I want to understand why this doesn't work the way I expect it to.

jsbin: http://jsbin.com/dosodeg/18

For some reason, Component1 doesn't re-render in jsbin's or Stackoverflow's in-app preview panes. Instead, Component2 is displayed briefly and then the page goes blank. Open the output in a separate window to see the actual behavior.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>App</title>
  <meta name="description" content="Troubleshooting SPA Component Navigation in React">
  <meta name="author" content="Jeremy McLain">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.1/react-dom.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser-polyfill.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/babel">
var App = React.createClass({
  getInitialState: function () {
    return {
      view: <Component1 setView={this.setView} />
    };
  },
  setView: function(view) {
    switch(view) {
      case "component1":
        this.setState({view: <Component1 setView={this.setView} />});
        break;
      case "component2":
        this.setState({view: <Component2 setView={this.setView} />});
        break;
    }
  },
  render: function () {
    return (
      <div>
        <h3>App</h3>
        { this.state.view }
      </div>
    );
  }
});

var Component1 = React.createClass({
  component2Click: function(event) {
    this.props.setView("component2");
  },
  render: function () {
    return (
      <div>
        <h4>Component1</h4>
        <p><a href="" onClick={this.component2Click}>Component2</a></p>
      </div>
    );
  }
});

var Component2 = React.createClass({
  component1Click: function(event) {
    this.props.setView("component1");
  },
  render: function () {
    return (
      <div>
        <h4>Component2</h4>
        <p><a href="" onClick={this.component1Click}>Component1</a></p>
      </div>
    );
  }
});

ReactDOM.render(
  <App />
  , document.getElementById("container"));
</script>
</body>
</html>
1

1 Answers

2
votes

Your links have an empty href. When you click them, it changes the current URL to the same value, which triggers a reload. You can remove the href attribute or use event.preventDefault() in the onClick handler.