question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Different behavior compared to native react.js: state sudden reset

See original GitHub issue

Hi. I tried to translate this simple code into scalajs-react:

/* Javascript */

// Just a plain Input object which saves user's input as a state
var Input = React.createClass({
    getInitialState: function() { return { value: '' } },

    onChanged: function(e) {
        this.setState({ value: e.target.value });
    },

    render: function() {
               // <input type="text" value={this.state.value} onChange={this.onChanged} />
        return React.createElement("input", {type: "text", value: this.state.value, onChange: this.onChanged});
    }
});

// Displays a number and an <Input> next to it
var TestCounter = React.createClass({
    render: function() {
                // <div>
                //    <span>{this.props.counter}</span>
                //    <Input />
               // </div>
        return React.createElement("div", {}, 
              React.createElement("span", {}, this.props.counter),
              React.createElement(Input)
         );
    }
});

// Launch the counter
var cnt = 0;
setInterval(function(){
    ReactDOM.render(React.createElement(TestCounter, {counter: cnt}), domNode);
    cnt ++;
}, 1000);

And the result:

/* Scala */

// Just a plain Input object which saves user's input as a state
class InputBackend($: BackendScope[Unit, String]) {
    def onChanged(e: ReactEventI): Callback ={
      $.modState((_) => {e.target.value} );
    }

    def render() = {
      <.input (
        ^.`type` := "text",
        ^.value := $.state.runNow(),
        ^.onChange ==> onChanged
      );
    }
  }

// Displays a number and an <Input> next to it
  class TextCounterBackend($: BackendScope[Int, Unit]) {
    def render() = {
      val Child = ReactComponentB[Unit]("Input")
        .initialState("this string wipes out any change in the input")
        .renderBackend[InputBackend]
        .build

      <.div(
        <.span($.props.runNow()),
        Child(Unit)
      );
    }
  }

  var cnt = 0;

// Launch the counter
  @JSExport
  override def main(): Unit = {
    setInterval(1000) {
      ReactDOM.render(
        ReactComponentB[Int]("TextCounter")
          .renderBackend[TextCounterBackend]
          .build(cnt),
        dom.document.getElementById("test_javascript_scala")
      );
      cnt += 1;
    };
  }

However, the result in the browser behaves differently from the original one:

  • in the first case (javascript) the counter is ticking and I can seamlessly modify the value of the input, the change is preserved from tick to tick;
  • in the 2nd case (scalajs-react) the counter is ticking, but any change I introduce is dropped to the initial one on each next tick.

Am I doing something wrong here?

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
japgollycommented, Mar 17, 2016

@stillwaiting It was a case of #255.

While we’re here, there are a few small improvements to suggest.

You can change

  def render() = {
    <.input (
      ^.`type` := "text",
      ^.value := $.state.runNow(),
      ^.onChange ==> onChanged
    );
  }

to

  def render(state: String) =
    <.input.text(
      ^.value := state,
      ^.onChange ==> onChanged)

and

  val inputBuilder = ReactComponentB[Unit]("Input")
    .initialState("this string wipes out any change in the input")
    .renderBackend[InputBackend]
    .build

  def render() = {
    <.div(
      <.span($.props.runNow()),
      inputBuilder(Unit)
    );
  }

to

  val inputBuilder = ReactComponentB[Unit]("Input")
    .initialState("this string wipes out any change in the input")
    .renderBackend[InputBackend]
    .buildU // ← add U here because it's Unit. (This won't be needed from the next version onwards.)

  def render(props: Int) =
    <.div(
      <.span(props),
      inputBuilder())
1reaction
japgollycommented, Mar 17, 2016

They’re different (hi btw) in that unlike the JS version, in your Scala version you’re creating the component inside the tick-loop. Which means React keeps thinking it’s got new components each tick.

Move this:

ReactComponentB[Int]("TextCounter")
  .renderBackend[TextCounterBackend]
  .build

to the top level, outside of the loop.

A common structure I use is to have an object with Props and/or State and/or Backend types defined inside, and a val Component = ReactComponentB ... .build so that everything pertaining to the component is all in one unit.

Like:

object TextCounter {
  type Props = Int
  type State = Unit
  final class Backend($: BackendScope[Props, State]) {
    ...
  }
  val Component = ...
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Preserving and Resetting State - React Docs Beta
State is isolated between components. React keeps track of which state belongs to which component based on their place in the UI tree....
Read more >
Avoid parent component resetting child components state
Each item should have a unique and stable key, otherwise React will unmount it and re-render it, causing the state resetting you experience....
Read more >
React Hooks cheat sheet: Best practices with examples
In this tutorial, we'll outline some React Hooks best practices and ... state value (not necessarily the initial state) and another function ...
Read more >
How to Manage State in a React App – With Hooks, Redux ...
Hi! In this article we'll take a look at the many ways you can manage state in a React app. We'll start by...
Read more >
5 Methods to Persisting State Between Page Reloads in React
One of the straightforward options is to use localStorage in the browser to persist the state. Let's take a look at an example....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found