Original codes run well, but when I change to replace React.createElement by JSX, it seems not working, why?

Viewed 40

I'm new using react and I'd like to convert from only react to JSX language.

The original codes run well, here are the original codes:

<script>

    class MyHead extends React.Component {
        render() {
            return React.createElement("LI", null, "ITEM" + this.props.level);
        }
    }

    class MyheadList extends React.Component {
        constructor(props) {
            super(props);
            this.state = { maxLevel: props.start };
        }

        componentWillMount() {
            this.intervalID = window.setInterval(() => {
                this.setState((currentState, currentProps) => {
                    if (currentState.maxLevel > currentProps.end) {
                        return currentState;
                    } else {
                        return { maxLevel: currentState.maxLevel + 1 }
                    }
                }
                );
            }, 1000);
        }

        componentWillUnount() {
            window.clearInterval(this.intervalID);
        }

        render() {
            let heads = [];
            let head;
            for (let i = 1; i < this.state.maxLevel; i++) {
                head = React.createElement(MyHead, { level: i });
                heads.push(head);
            }
            return React.createElement("UL", null, heads);
        }
    }

    window.addEventListener("load", () => {
        let reactElement = React.createElement(MyheadList, { start: 1, end: 4 });
        ReactDOM.render(
            reactElement, document.body
        );
    });
</script>

Those above codes run well, but when I change to replace React.createElement by JSX, it seems not the same logic outcome as the original codes, why ?

I only remove all React.createElement and use JSX syntax to replace it. Besides, all other codes are the same as the original one.

What codes I modify are like below:

<script type="text/babel">

    class MyHead extends React.Component {
        render() {
            return <li>"ITEM" {this.props.level}</li>;
        }
    }

    class MyheadList extends React.Component {

        constructor(props) {
            super(props);
            this.state = { maxLevel: props.start };
        }

        componentWillMount() {
            this.intervalID = window.setInterval(() => {
                this.setState((currentState, currentProps) => {
                    if (currentState.maxLevel > currentProps.end) {
                        return currentState;
                    } else {
                        return { maxLevel: currentState.maxLevel + 1 }
                    }
                });
            }, 1000);
        }

        componentWillUnount() {
            window.clearInterval(this.intervalID);
        }


        render() {
            let heads = [];
            let head;
            for (let i = 1; i < this.state.maxLevel; i++) {
                head = <MyHead level={i} />;
                heads.push(head);
            }
            return <ul>{heads}</ul>;
        }
    }

    window.addEventListener("load", () => {
        let reactElement = <MyheadList start="1" end="4" />;
        ReactDOM.render(
            reactElement, document.body
        );
    });
</script>
1 Answers

Issue

It seems the issue might've come down to string concatenation versus math, or the javascript types. The start and end props are string values, and when setting the initial maxValue state from props it retains the string type.

Later when comparing the current maxLevel state value to the end prop value it is a coincidence that the char values work out mathematically.

console.log("1" < "2"); // true
console.log("2" < "1"); // false
console.log(1 < "2");   // true
console.log("2" < 1);   // false

The maxLevel was actually having 1 appended to it each "tick". Type coercion between number and string is a little funny, it results in concatenation instead of addition.

console.log(1 + 1);     // 2
console.log("1" + 1);   // "11"
console.log(1 + "1");   // "11"
console.log("1" + "1"); // "11"

Solution

Though I haven't quite made sense of it, converting maxLevel to a number type resolves the odd rendering result. I don't think it makes a huge difference in this code, componentWillMount has essentially been deprecated in React v16, preferring the componentDidMount lifecycle method. Added a React key for the mapped array for completeness.

constructor(props) {
  super(props);
  this.state = { maxLevel: Number(props.start) };
}

When comparing two values you should strive to do the comparison within the same type, so convert the end prop value to a number for comparison.

if (currentState.maxLevel > Number(currentProps.end)) {

class MyHead extends React.Component {
  render() {
    return <li>"ITEM" {this.props.level}</li>;
  }
}

class MyheadList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { maxLevel: Number(props.start) };
  }

  intervalID = null;

  componentDidMount() {
    this.intervalID = window.setInterval(() => {
      this.setState((currentState, currentProps) => {
        if (currentState.maxLevel > Number(currentProps.end)) {
          return currentState;
        } else {
          return { maxLevel: currentState.maxLevel + 1 };
        }
      });
    }, 1000);
  }

  componentWillUnount() {
    window.clearInterval(this.intervalID);
  }

  render() {
    const heads = [];
    for (let i = 1; i < this.state.maxLevel; i++) {
      heads.push(<MyHead level={i} key={i} />);
    }
    return <ul>{heads}</ul>;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <MyheadList start="1" end="4" />,
  rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.7.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.7.0/react-dom.min.js"></script>
<div id="root"></div>

Related