9

I have a React app that includes a form, which is rendered server side, prepopulated with the user's work in progress. The problem is that if the user edits a value in the form before the app loads, then the app is unaware of the change. When the user saves, the unchanged data that was rendered by the server is re-saved, and the user's new data is dropped, although it is still shown in the form. In short, there seems to be a disconnect between React and input values in the markup that it replaces when initially loading.

I suppose I could put refs on every input and copy their values into the application state on componentDidMount, but there has got to be a better way. Has anyone else solved this problem?

Update

I am now of the opinion that the best way to solve this would be to have React take input values into account when creating checksums. GH issue: https://github.com/facebook/react/issues/4293

Dan Ross
  • 3,596
  • 4
  • 31
  • 60
  • "before the app loads" have you actually been able to do this? I'm pretty sure it's impossible if you're including the initial data and scripts in the html. – Brigand Jul 05 '15 at 06:16
  • The app is rather large at the moment, and it takes a second or so to load (from a script src). Also, we are targeting a user base that has bad latency on mobile. – Dan Ross Jul 05 '15 at 06:49
  • Just to clarify, the data is embedded in a script tag, but the app is loaded from the src attribute of a script tag. – Dan Ross Jul 05 '15 at 06:50
  • An easy way to test this is on chrome, change the network to slow 3g so the js takes a few seconds to load – GeeDawg Jul 13 '18 at 01:12

2 Answers2

4

I suppose I could put refs on every input and copy their values into the application state on componentDidMount, but there has got to be a better way. Has anyone else solved this problem?

Browsers autofilling fields or remembering previous values across refreshes can also cause what is effectively the same issue - your app's view of the data being different from the user's.

My brute-force solution in the past has been to extract the complete form state from its inputs onSubmit and re-run validaton before allowing submission to proceed.

Using componentDidMount as you suggest sounds like a more elegant solution as it avoids the user entering invalid data and being allowed to try to submit it before they get any warnings. You don't need to add a ref to every input, though; just add one to the <form> and use its .elements collection to pull all the data.

Suggested solution:

  1. In componentDidMount(), pull the form's data from its .elements (I extracted get-form-data from my form library for this purpose)
  2. Check each field's current value against what's in your app's state.
  3. If a field's current value is different, treat it just as you would new user input arriving via an event - update it in state and re-run any associated validation routines.

Then from componentDidMount() onwards, your app and the user will always be on the same page (literally).

Jonny Buchanan
  • 61,926
  • 17
  • 143
  • 150
0

If I understand you correctly, this is what is happening: https://jsfiddle.net/9bnpL8db/1/

var Form = React.createClass({
    render: function () {
        return <div>
            <input type="text" defaultValue={this.props.defaultValue}/>
        </div>;
    }
});

// Initial form render without server-side data
React.render(<Form/>, $('#container')[0]);

setTimeout(function () {
    // Simulate server-side render
    $('#container').html(
        // overwrites input
        React.renderToString(<Form defaultValue="server1"/>)
    );
}, 5000);

Server-side rendering should not be used to make updates. Rather, server-side rendering should be used to create the initial page, populating the inputs before the user ever has a chance to make changes. To solve your problem, I think you have a couple of options:

  1. Use server-side rendering to render the initial page, prepopulating the input with saved data. With this option, the user does not ever see the page without the saved data. This removes the issue where the user starts entering input and then has that input overwritten by the server.
  2. Use client-side rendering to render the page and use a GET request to get the prepopulated data, updating the input if the user hasn't already entered some input.
knowbody
  • 8,106
  • 6
  • 45
  • 70
Matthew King
  • 1,342
  • 7
  • 13
  • Actually, #1 is exactly what I am doing: `React.render(, document.body)` mounts over the server-rendered form when the app loads. Before the app loads, the user sees their data in a form, and can start editing it. However, when the app finally does mount over the form, and loads the previously dehydrated data from `window.context`, it is unaware of the user's recent changes. Not only that, but because the DOM has not changed, React does not bother rerendering; the user still sees their changes in the form, and the app is unaware of those changes. – Dan Ross Jul 05 '15 at 06:45
  • #2 would suffer from a similar problem: the user might decide to edit the apparently blank form before the app loads, and then save immediately after is loads. Once again, the app would be unaware of the user's changes, and React would have had no reason to rerender over top of them. – Dan Ross Jul 05 '15 at 06:48
  • Ah ok, I understand your question now. I think your initial hunch was correct, there isn't any shortcut. I think we need to add code in componentDidMount that checks for existing input, since otherwise that input will be overwritten by whatever props are passed down. – Matthew King Jul 05 '15 at 07:22
  • @DanRoss so this is what is happening in our app: https://jsfiddle.net/88fj46Lm/1/ Look in the console to see the timing. In that case, it should be sufficient to read form input data using this.refs on component mount. – Tomas Holas Jul 05 '15 at 08:30