inProgress-team / react-native-meteor

Meteor Reactivity for your React Native application :)
MIT License
693 stars 210 forks source link

Reactivity on Meteor.loginWithPassword vs Accounts.createUser #234

Open bsbechtel opened 7 years ago

bsbechtel commented 7 years ago

I am using react-navigation, along with version 1.0.3 of react-native-meteor, and it appears that Accounts.createUser doesn't reactively update the user object in the same way Meteor.loginWithPassword does.

I originally thought this was a react-native re-rendering issue with react-navigation, but after moving around the screens and structure of my components, along with testing for re-rendering via console.log statements everywhere in my code, I am thinking this is just different behavior found in this package (possibly related to behavior related to AsyncStorage? see issue #19)

My code is roughly as follows:

user data container:

export const AppContainer = createContainer(() => {
  return {
    loggedIn: !!Meteor.user(),
    authInProgress: Meteor.loggingIn(),
    connected: Meteor.status().connected,
  };
}, App);

App:

export const App = ({ authInProgress, loggedIn, connected }) => {
  const screenProps = {
    loggedIn,
    authInProgress,
  };
  return (
    loggedIn ?
      <Routes /> :
      <AuthStack
        screenProps={screenProps}
      />
  );
}

AuthStack:

export const AuthStack = StackNavigator({
  Login: {
    screen: Login,
  },
  Register: {
    screen: Register,
  },
}, {
  headerMode: 'none',
});

Login:

export class Login extends Component {
  static propTypes = {
    screenProps: PropTypes.object,
    navigation: PropTypes.object,
  }
  state = {
    email: '',
    emailError: '',
    password: '',
    passwordError: '',
    authError: '',
  }
  resetErrors = () => {
    this.setState({
      emailError: '',
      passwordError: ''
    });
  }
  login = () => {
    this.resetErrors();

    let email = this.state.email.trim();
    let password = this.state.password.trim();

    // validate
    isEmail(email) ? null : this.setState({emailError: 'Please provide a valid email'});
    isValidPassword(password) ? null : this.setState({passwordError: 'Please provide a password longer than 6 characters'});
    isNotEmpty(email) ? null : this.setState({emailError: 'Please provide an email'});
    isNotEmpty(password) ? null : this.setState({passwordError: 'Please provide a password'});

    if (isNotEmpty(email) && isNotEmpty(password) && isEmail(email) && isValidPassword(password)) {
      // login
      Meteor.loginWithPassword(email, password, (error) => {
        if (error && error.reason === 'Incorrect password') {
          this.setState({
            passwordError: error.reason,
            email,
          });
        } else if (error && error.reason === 'User not found') {
          this.setState({
            emailError: error.reason,
            email,
          });
        } else if (error) {
          this.setState({
            authError: error.reason,
            email,
          });
        }
      });
    }
  }
  handleEmailChange = (email) => this.setState({ email });
  handlePasswordChange = (password) => this.setState({ password });
  render() {
    const { screenProps, navigation } = this.props;

    if (screenProps.authInProgress) {
      return <Loader />;
    }

    return (
      <Container>
        <AuthForm
          formState={this.state}
          handleSubmit={this.login}
          submitText={'Login'}
          handleEmailChange={this.handleEmailChange}
          handlePasswordChange={this.handlePasswordChange}
        />
        <ForgotPassword>Forgot Password?</ForgotPassword>
        <Register
          onPress={() => navigation.navigate('Register')}
        >Sign up</Register>
      </Container>
    );
  }
}

Register:

export class Register extends Component {
  static propTypes = {
    screenProps: PropTypes.object,
    navigation: PropTypes.object,
  }
  state = {
    email: '',
    emailError: '',
    password: '',
    passwordError: '',
    authError: '',
  }
  resetErrors = () => {
    this.setState({
      emailError: '',
      passwordError: ''
    });
  }
  register = () => {
    this.resetErrors();

    let email = this.state.email.trim();
    let password = this.state.password.trim();

    // validate
    isEmail(email) ? null : this.setState({emailError: 'Please provide a valid email'});
    isValidPassword(password) ? null : this.setState({passwordError: 'Please provide a password longer than 6 characters'});
    isNotEmpty(email) ? null : this.setState({emailError: 'Please provide an email'});
    isNotEmpty(password) ? null : this.setState({passwordError: 'Please provide a password'});

    if (isNotEmpty(email) && isNotEmpty(password) && isEmail(email) && isValidPassword(password)) {
      // Register
      Accounts.createUser({
        email,
        password,
      }, (error) => {
        if (error) {
          this.setState({
            authError: error.reason,
            email,
          });
        } else {
          // hack to reactively login w/ react-native-meteor
          Meteor.loginWithPassword(email, password, (error) => {
            if (error) {
              this.setState({
                authError: error.reason,
                email,
              });
            }
          });
        }
      });
    }
  }
  handleEmailChange = (email) => this.setState({ email });
  handlePasswordChange = (password) => this.setState({ password });
  render() {
    const { screenProps, navigation } = this.props;

    if (screenProps.authInProgress) {
      return <Loader />;
    }

    return (
      <Container>
        <AuthForm
          formState={this.state}
          handleSubmit={this.register}
          submitText={'Register'}
          handleEmailChange={this.handleEmailChange}
          handlePasswordChange={this.handlePasswordChange}
        />
        <Login
          onPress={() => navigation.goBack()}
        >Login</Login>
      </Container>
    );
  }
}

To work around this issue, you can see I called Meteor.loginWithPassword() on a successful Accounts.createUser() call, but I don't think things should really work this way.

I wanted to open this issue to see if there was a difference in how the two methods are implemented in this package that changes how Meteor.user() is reactively updated. Thanks!

PolGuixe commented 7 years ago

@bsbechtel I am having the same problem... Any luck?

bsbechtel commented 7 years ago

No, I just logged in when receiving the result and haven't touched the code since. It would be nice if one of the maintainers could provide some input.

conorstrejcek commented 7 years ago

@PolGuixe @bsbechtel I had a similar issue and was able to work around it here: https://github.com/inProgress-team/react-native-meteor/issues/239

I did the same thing as you (calling Meteor.loginWithPassword() within an Accounts.createUser(). I also found another bug with the userId getting unset on reconnects and solved it with an internal react-native-meteor function. I don't think any of these solutions is ideal however.