0

I'm new to Meteor and I'm studying it. Actually I'm creating a User login, Signup page with a private control panel for each registered user.

I'm able to Signup and Login correctly but I I have two different issues:

  1. On create user I can not save information to the profile object and it looks like the onCreateUser method is not fired at all.
  2. When the user log in I get user information in the Control Panel page, but if I refresh the page I do not receive those information anymore even if the user is still logged in.

Here is the code.

/imports/api/users.js:

import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';

if (Meteor.isServer) {
  Meteor.publish('userData', () => {
    return Meteor.users.find({ _id: Meteor.userId() }, {
      fields: { profile: 1 }
    });
  });
}

Accounts.onCreateUser((options, user) => {
  user.profile = options.profile || {};
  user.profile.accountType = options.accountType;

  return user;
});

/imports/ui/Signup.js:

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import Joi from 'joi';
import validation from 'react-validation-mixin';
import strategy from 'joi-validation-strategy';
import classnames from 'classnames';

import history from '../../utils/history';

class Signup extends Component {
  constructor(props) {
    super(props);

    this.validatorTypes = {
      accountType: Joi.string().required().label('Account type'),
      email: Joi.string().email().label('Email'),
      password: Joi.string().required().min(6).label('Password'),
      confirmPassword: Joi.string().required().min(6).valid(Joi.ref('password')).label('Confirm password').options({
        language: {
          any: {
            allowOnly: '!!Passwords do not match'
          }
        }
      })
    };

    this.getValidatorData = this.getValidatorData.bind(this);
    this.renderHelpText = this.renderHelpText.bind(this);
    this.getClasses = this.getClasses.bind(this);
    this.onSubmit = this.onSubmit.bind(this);

    this.state = {
      serverError: ''
    };
  }

  componentWillMount() {
    if (Meteor.userId()) {
      history.replace('/');
    }
  }

  getValidatorData() {
    return {
      accountType: this.refs.accountType.value,
      email: this.refs.email.value,
      password: this.refs.password.value,
      confirmPassword: this.refs.confirmPassword.value
    };
  }

  renderHelpText(message) {
    return (
      <span className='validation-error-message'>{message}</span>
    );
  }

  getClasses(field) {
    return classnames({
      'form-group': true,
      'has-error': !this.props.isValid(field)
    });
  }

  onSubmit(e) {
    e.preventDefault();

    const onValidate = error => {
      if (!error) {
        let accountType = this.refs.accountType.value;
        let email = this.refs.email.value.trim();
        let password = this.refs.password.value.trim();
        let confirmPassword = this.refs.confirmPassword.value.trim();

        Accounts.createUser({ email, password, accountType }, (err) => {
          if (err) {
            this.setState({
              serverError: err.reason
            });
          } else {
            this.setState({
              serverError: ''
            });
            history.replace('/account');
          }
        });
      }
    };

    this.props.validate(onValidate);
  }

  render() {
    return (
      <div className="form-container">
        <h3>Create Account</h3>
        <form onSubmit={this.onSubmit}>
          <div className={this.getClasses('accountType')}>
            <select
              className="form-control"
              name="accountType"
              ref="accountType"
              placeholder="Account type"
              onChange={this.props.handleValidation('accountType')}
            >
              <option value="">Select account type</option>
              <option value="student">Sudent</option>
              <option value="teacher">Teacher</option>
              <option value="guest">Guest</option>
            </select>
            {this.renderHelpText(this.props.getValidationMessages('accountType')[0])}
          </div>
          <div className={this.getClasses('email')}>
            <input
              className="form-control"
              type="text"
              name="email"
              ref="email"
              placeholder="email address"
              onChange={this.props.handleValidation('email')}
            />
            {this.renderHelpText(this.props.getValidationMessages('email')[0])}
          </div>
          <div className={this.getClasses('password')}>
            <input
              className="form-control"
              type="password"
              name="password"
              ref="password"
              placeholder="password"
              onChange={this.props.handleValidation('password')}
            />
            {this.renderHelpText(this.props.getValidationMessages('password')[0])}
          </div>
          <div className={this.getClasses('confirmPassword')}>
            <input
              className="form-control"
              type="password"
              name="confirmPassword"
              ref="confirmPassword"
              placeholder="confirm password"
              onBlur={this.props.handleValidation('confirmPassword')}
            />
            {this.renderHelpText(this.props.getValidationMessages('confirmPassword')[0])}
          </div>
          <button className="btn btn-dark" type="submit">Create</button>
        </form>
        <div className="meta">
          <p>Already have an account? <Link to="/login">Login</Link></p>
        </div>
      </div>
    );
  }
};

export default validation(strategy)(Signup);

What I expect is when I create a new use the method in users.js called Accounts.onCreateUser get fired and the accountType information is added to profile. It doesn't happen.

I also expect to always retrieve the current logged in user information in the control panel. Here is the control panel component:

/imports/ui/Account.js:

import React, { Component } from 'react'; import { Meteor } from 'meteor/meteor';

import history from '../../utils/history';

class Account extends Component {
  constructor(props) {
    super(props);

    Meteor.subscribe('userData');

    this.state = {
      user: {}
    }
  }

  componentWillMount() {
    if (!Meteor.userId()) {
      history.replace('/login');
    }
  }

  componentDidMount() {
    console.log(Meteor.userId()); // I can always get this value
    const user = Meteor.users.find({ _id: Meteor.userId() }, {
      fields: { profile: 1 }
    }).fetch();
    console.log(user); // On refresh it is an empty array [].

    this.setState({ user });
  }

  render() {
    return (
      <div>Hello Account</div>
    );
  }
};

export default Account;

What I'm doing wrong?

Ayeye Brazo
  • 3,316
  • 7
  • 34
  • 67

1 Answers1

0

There are several things wrong with your code but they can easily be fixed.

  1. Accounts.onCreateUser is a server only function. Insert a console.log into the function just to make sure it is called. If not, make sure that you import '/imports/api/users.js' in your main.js.

  2. Accounts.createUser accepts only 4 keys in the options object: username, email, password and profile (see docs). That is why accountType is not passed to onCreateUser. Do Accounts.createUser({ email, password, profile: { accountType } }, ...). Then in onCreateUser you find the accountType under options.profile.accountType.

  3. To subscribe to data you should use meteors createContainer component. See the guide. That will solve the problem that your data is not yet available when the component mounts and will rerender it when the data is ready.

tomsp
  • 1,797
  • 14
  • 17
  • Thanks for your detailed answer. I will test it out! Cheers – Ayeye Brazo Aug 05 '17 at 11:12
  • Well, I forgot to import my `users.js` to `main.js` as you mentioned. Now the profile is correctly saving my data. I do not understand very well your 3rd point. In all guides I followed on Udemy that `createContainer` component has never been mentioned. Why I cannot just query for user data from `componentDidMount`? Can you please write a more detailed example on how to get user data using your approach? Following that guide I'm not able to achieve it. Cheers. – Ayeye Brazo Aug 05 '17 at 11:42
  • look at my answer here: https://stackoverflow.com/questions/44299378/meteor-subscribe-first-fetch-empty-data-then-real-data/44300276#44300276 – tomsp Aug 05 '17 at 12:44
  • `componentDidMount` will only run once, `createContainer` will rerun when the data is ready. – tomsp Aug 05 '17 at 12:50