Giter Site home page Giter Site logo

Comments (6)

dvdzkwsk avatar dvdzkwsk commented on April 28, 2024

I read through the article but didn't fully understand what the problem was (though from the title I expect I understand it) or what caused it:

React and Redux get initialized on the client side, unaware of the input values of my input fields.

Why is this the case? Shouldn't they have initial state rehydrated from the server (provided by window.__INITIAL_STATE__ [or whatever]). Maybe I'm missing something obvious, as I haven't yet done server-side rendering for forms, so perhaps I can throw together a small demo to see if I can reproduce the issue.

from react-reformed.

dvdzkwsk avatar dvdzkwsk commented on April 28, 2024

Scratch that last (deleted) comment, I cannot reproduce the issue. If you take a look at this branch: https://github.com/davezuko/react-reformed/tree/server-demo (npm i && npm run server), you'll see that the form gets properly rehydrated. Is there something else that I need to do to properly reproduce the issue?

Server rendering code is here: https://github.com/davezuko/react-reformed/blob/server-demo/demo/server/app.js. The form looks right both loaded as a static page (i.e. no React app to resume on the client) and with the backing client app.

So basically, where I'm at with this is: I don't really have a unique way of approaching this. The form is just rendered straight through props, without any ref usage (which I think is what redux-form uses, which may be the source of their troubles and thus necessitates the workaround, but I could be wrong), so for us it should just work if you are rehydrating the state correctly.

If I'm missing something, please let me know.

from react-reformed.

AndrewIngram avatar AndrewIngram commented on April 28, 2024

When i'm talking about autofill, i'm referring to browser behaviour to fill in fields that it recognises with values from it's database, i.e. when Chrome makes the field backgrounds turn yellow.

With client-only rendering, this happens:

  • Page loads
  • Javascript runs
  • React initialises and renders the DOM (including form)
  • Browser recognises the auto-fillable fields and populates them, triggering the change event.
  • Model is correctly updated

With server-side rendering:

  • Initial render happens on the server
  • Page loads
  • Browser recognises the auto-fillable fields and populates them
  • Javascript runs
  • React initialises, but doesn't change the DOM, because the checksums match.
  • No change event is triggered for the form fields, because that happened earlier.
  • The model hasn't been updated.

This is specific example of the general issue with forms and server-side rendering, which is that if you're using a state object to hold form values, it's possible for them to get out of sync with the DOM, because changes can happen before React is initialized and able to handle them.

The solution in the article reads the current values of the fields from the DOM when the form is mounted. It's the only solution i'm aware of for this type of problem, it's just unfortunately because it breaks the purity of never reading the DOM that we get with the client-only solution.

from react-reformed.

dvdzkwsk avatar dvdzkwsk commented on April 28, 2024

Ok, I understand the problem now, sorry for being so dense. For clarity, what I was missing was that extra step in the sequence of events, since the auto-filling does work. You hint at it in one of the points, but I think it's crucial to point out explicitly:

  • Initial render happens on the server
  • Page loads
  • Browser recognizes the auto-fillable fields and populates them
  • User changes some fields (before [client] React initializes)
  • Javascript runs
  • React initializes, but doesn't change the DOM, because the checksums match.
  • No change event is triggered for the form fields, because that happened earlier.
  • The model hasn't been updated. It's now out of date with what's visible in the form.

I've seen this problem brought up before, in fact the first place I saw it tackled was in an Angular 2 talk (found it, here: https://www.youtube.com/watch?v=0wvZ7gakqV4#t=10m30s). Their approach (called preboot) is to load some additional JS (likely in the head) that will run before the app finishes initializing. The script will listen to and record user events and then play them back to the app behind the scenes once it initializes. This is conceptually very cool but unfortunately probably not a worthwhile investment for smaller apps in React land.

The refs solution is indeed ugly and I'm not a fan of it at all, but for right now I can't think of any more sane of an approach (that doesn't require significantly more work). So, per your initial question: no, I unfortunately don't have an alternate approach. The use of refs seems like a necessary workaround, but now that I understand the problem better I'll play around with it and see what other ideas I can come up with.

from react-reformed.

dvdzkwsk avatar dvdzkwsk commented on April 28, 2024

So I just did come up with an idea, but I think explicit ref usage is still preferable for clarity.

const rehydrateFromDOM = (WrappedComponent) => {
  class RehydratableFromDOM extends React.Component {
    componentDidMount () {
      const form = ReactDOM.findDOMNode(this._form)

      // this currently only looks for inputs, and is pretty specific, so it would
      // have to be expanded to fit your needs.
      const model = [...form.querySelectorAll('input')]
        .reduce((acc, input) => {
          switch (input.type) {
            case 'checkbox':
              if (input.checked) {
                acc[input.name] = (acc[input.name] || []).concat(input.value)
              }
              break
            default:
              acc[input.name] = input.value
          }
          return acc
        }, {})
      this.props.setModel(model)
    }

    _onRef = (el) => {
      this._form = el
    }

    render () {
      return React.createElement(WrappedComponent, {
        ...this.props,
        ref: this._onRef,
      })
    }
  }
  return RehydratableFromDOM
}

Then you can use it as follows:

const createFormContainer = compose(
  reformed(),
  rehydrateFromDOM,
)

createFormContainer(YourForm)

I delayed rendering, messed with the inputs, and then initialized the client application manually. It correctly rehydrated the model from what existed in the DOM, so it at least works on a prototype level. The one big gotcha with this, of course, is that none of the higher order components can be stateless since they cannot have refs attached to them. I guess you could render the wrapped component in a div and just run the querySelector off of that, but it seems wrong to insert a <div> unexpectedly.

from react-reformed.

tstirrat15 avatar tstirrat15 commented on April 28, 2024

I found this issue when I was looking into the same problem, and I found the preboot library. I've yet to implement it, but it seems like it may be a good way around these problems.

It works by being the first thing to load on the page, and then listening for input events, which can then be played back after the client code has rendered/bootstrapped.

from react-reformed.

Related Issues (12)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.