studiointeract / accounts-ui

Accounts UI for React in Meteor 1.3+
MIT License
196 stars 80 forks source link

DraftJS Kills <Accounts.ui.LoginForm /> - I don’t know why! #96

Open JeremyIglehart opened 7 years ago

JeremyIglehart commented 7 years ago

I'm really not sure what is happening here. When I click on the "DraftJS" contentEditable - the <Accounts.ui.LoginForm /> instantly disappears. I haven't the foggiest idea why.

Demo: draftjsvaporizingloginformwithinspector

To see the bug in its current state:

Go to: http://draftjsmeteor.autoschematic.com/

To duplicate the problem in your own development environment:

  1. Clone this repository: https://github.com/JeremyIglehart/DraftJSMeteor
  2. Create a settings-development.json file (you can leave it blank)
  3. Run with npm start

The main question:

Does anyone know why DraftJS kills the <Accounts.ui.LoginForm />?

After looking into this problem for two days now, I suspect the problem is hiding somewhere in how <Accounts.ui.LoginForm /> is doing something - perhaps in their STATES API? I'm really confused here. Any help would really be appreciated.

Problems I've ruled out:

  1. It's not a Session Variable problem. DraftJS doesn't use them - in fact, there are no Session Variables being used at all right now as far as I can detect (using Meteor Toys)
  2. It is not DraftJS killing the page somehow. The reason why is because after the <Accounts.ui.LoginForm /> component I have placed a regular vanilla <p> tag and it is left alone.

It doesn't seem to me this problem has anything to do with Meteor, or React specifically.

Where I'm looking now to solve this:

  1. something to do with std:accounts-ui
  2. I have no idea where else to look. Based on the gif demo above you can see that the div with the className accounts-ui is rendering just fine - after clicking in the DraftJS contentEditable area, however, something inside this component breaks.

Issues Tracking this Bug:

I've created three issues to try and track this down:

Once I find a solution I'll update all of the issues, forums and questions I've posted everywhere.

JeremyIglehart commented 7 years ago

Okay, figured it out (I think)

std:accounts-ui just doesn't maintain any kind of state. If react decides to re-render the form for any reason it totally kills <Accounts.ui.LoginForm />. I'm not sure this is right - to me, a component should live on if things get re-rendered (who knows what else is going on where they get rendered). It should also maintain some kind of internal state. If the initial state was for signup, but somebody clicks login (and then something else re-renders in react) the form should just re-render right where you're at.

Weirdness.

Anyways, here's the code to test it to see what I'm talking about:

import React, { Component } from 'react'
import { Accounts, STATES } from 'meteor/std:accounts-ui';

class MyEditor extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <button onClick={() => this.forceUpdate()}>Rerender</button>
        <Accounts.ui.LoginForm formState={STATES.SIGN_UP} />
      </div>
    )
  }
}

export default MyEditor;

The code above always returns to the state passed to it in the props.

import React, { Component } from 'react'
import { Accounts, STATES } from 'meteor/std:accounts-ui';

class MyEditor extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <div>
        <button onClick={() => this.forceUpdate()}>Rerender</button>
        <Accounts.ui.LoginForm />
      </div>
    )
  }
}

export default MyEditor;

This is even worse! because something re-renders the page, the whole form just vanishes for no reason.

So - This issue is closed, I'm going to investigate further and find out if there is a better way of maintaining state. If not, I'm going to post a feature request issue.

Thanks so much for your hard work!

JeremyIglehart commented 7 years ago

Okay - no, something is really wrong here. Because if you force the state to be in sign-up mode the form doesn't respond to show that you're logged in when you're logged in.

If somebody could please take a look at this and explain, I would really appreciate it.

JeremyIglehart commented 7 years ago

Thanks to Sean (arist0tl3) and his comment and his fork

It's interesting what he did there. Basically, I was calling <Accounts.ui.LoginForm /> from within the same "component" where the DraftJS <Editor /> component was called - like this:

// Imports

class MyEditor extends Component {

// Other component stuff

return (
  <div className="editor-container">
    <h1>DraftJS and Meteor Editor:</h1>
    <IconButton onClick={this._onBoldClick.bind(this)} touch={true} tooltip="Format Bold" tooltipPosition="top-right">
      <FontIcon className="material-icons" style={iconStyles}>format_bold</FontIcon>
    </IconButton>
    <Editor
      editorState={this.state.editorState}
      onChange={this.onChange}
      handleKeyCommand={this.handleKeyCommand.bind(this)}
      />
    <RaisedButton label="Log Editor State to Console" onClick={this.logState.bind(this)} primary={true} style={buttonStyle} />
  </div>

  <Accounts.ui.LoginForm />

  <p>This is a test - do I stay?</p>
)

// Exports

and Sean tried taking <Accounts.ui.LoginForm /> and moving it into a different react component and then calling the two different components from a parent component. Here's what he did in code:

  1. Removed <Accounts.ui.LoginForm /> from <MyEditor />
// Imports

class MyEditor extends Component {

// Other component stuff

return (
  <div className="editor-container">
    <h1>DraftJS and Meteor Editor:</h1>
    <IconButton onClick={this._onBoldClick.bind(this)} touch={true} tooltip="Format Bold" tooltipPosition="top-right">
      <FontIcon className="material-icons" style={iconStyles}>format_bold</FontIcon>
    </IconButton>
    <Editor
      editorState={this.state.editorState}
      onChange={this.onChange}
      handleKeyCommand={this.handleKeyCommand.bind(this)}
      />
    <RaisedButton label="Log Editor State to Console" onClick={this.logState.bind(this)} primary={true} style={buttonStyle} />
  </div>
)

// Exports

Created a <LogIn /> component:

// Imports

class LogIn extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <div className="login-container">

        <Accounts.ui.LoginForm />

        <p>This is a test - do I stay?</p>
      </div>
    )
  }
}

// Exports

Then called the two from a parent <Home /> component:

class Home extends Component {
  render () {
    return (
      <div>
        <MyEditor />
        <LogIn />
      </div>
    )
  }
}

I'm not actually sure why this works... but it does. It seams to me as though <Accounts.ui.LoginForm /> should be able to handle "re-rendering". I guess I just don't understand React well enough to understand why this separation is needed.

The million dollar question:

Is this a bug, or part of the intended design?

The two-million dollar question:

Is there come explanation of why this is the way it is that I should know?

Like, why is it normal that <Accounts.ui.LoginForm /> can't handle arbitrary re-renders without breaking?


To sweeten the deal a little bit - I've created a StackOverflow question to this extent. - Now it's worth points! :)

Elviron commented 6 years ago

Is this still an issue, @JeremyIglehart?