easoncxz / twitanalysis

Dig your Twitter data
https://easoncxz.github.io/twitanalysis
Other
1 stars 0 forks source link

Figure out why this React code doesn't do what it is trying to #2

Closed easoncxz closed 4 years ago

easoncxz commented 4 years ago

Notably, I'm trying to get the "Clear input" button to make the input box empty.

Expected behaviour:

Actual behaviour:

I'm working with this code on the React homepage, so it's perhaps most convenient to copy my code and paste it into the left-hand side code-box half of section "An Application" on https://reactjs.org/.

class TodoApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = { items: [], text: '' };
    this.handleChange = this.handleChange.bind(this);
    // this.handleSubmit = this.handleSubmit.bind(this);
    this.clearInputBox = this.clearInputBox.bind(this);
  }

  render() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={e => e.preventDefault()}>
          <label htmlFor="new-todo">
            What needs to be done?
          </label>
          <input
            id="new-todo"
            onChange={this.handleChange}
            value={this.state.text}
          />
          <button onClick={this.lodgeNewTask.bind(this)}>
            Add #{this.state.items.length + 1}
          </button>
          <button onClick={this.clearInboxBox}>
            Clear input
          </button>
        </form>
      </div>
    );
  }
  clearInputBox(e) {
    alert('clearing input bo x, ok???');
    this.setState(() => { text: '' });
  }

  handleChange(e) {
    this.setState({ text: e.target.value });
  }
  lodgeNewTask(e) {
    if (this.state.text.length === 0) {
     return;
    }
    const newItem = {
      text: this.state.text,
      id: new Date(),
    };
    this.setState((state) => {
      if (state !== this.state) {
        alert("The states aren't the same!");
      }
      return {
        items: this.state.items.concat(newItem),
        text: ''
      };
    });
  }

}

class TodoList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.items.map(item => (
          <li key={item.id.valueOf()}>{item.text}<br />{item.id.toISOString()}</li>
        ))}
      </ul>
    );
  }
}

ReactDOM.render(
  <TodoApp />,
  document.getElementById('todos-example')
);
easoncxz commented 4 years ago

After getting some help from a friend, I have made the code work via the following changes:

--- before.js   2020-08-22 02:16:34.000000000 +1200
+++ after.js    2020-08-22 02:16:25.000000000 +1200
@@ -24,7 +24,7 @@
           <button onClick={this.lodgeNewTask.bind(this)}>
             Add #{this.state.items.length + 1}
           </button>
-          <button onClick={this.clearInboxBox}>
+          <button onClick={this.clearInputBox}>
             Clear input
           </button>
         </form>
@@ -32,8 +32,9 @@
     );
   }
   clearInputBox(e) {
-    alert('clearing input bo x, ok???');
-    this.setState(() => { text: '' });
+    this.setState((state) => {
+      return Object.assign({}, state, { text: '' });
+    });
   }

Doh!!

Turns out I made two mistakes:

  1. A typo! After working a few years in strongly statically typed languages, I'm so reliant on a type-checker now that apparently I cannot make it past the homepage of a new library without tripping myself over.
  2. Some mistake/misunderstanding with the Component.prototype.setState method.

The typo was what took me forever to find; I think without the second pair of eyes (or a type-checker!), I would spend hours before finding this bug.

As for setState: from what I gathered with some fiddling around, it seems to support two APIs:

I mixed the two, and passed a lambda, but a lambda that returned a patch (type (s: StateType) => Partial<StateType>). Nope, that ain't gonna work!

So this issue is resolved now.