Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use redux-localize-redux with shared validation library #39

Closed
discrete opened this issue Dec 25, 2017 · 2 comments
Closed

How to use redux-localize-redux with shared validation library #39

discrete opened this issue Dec 25, 2017 · 2 comments
Labels

Comments

@discrete
Copy link

I am writing validation library for redux-form using redux-localize-redux.

The followings are snippets from my project.

loginform.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import compose from 'recompose/compose';
import { getTranslate, getActiveLanguage, addTranslation } from 'react-localize-redux';
import {
  Checkbox,
  RadioButtonGroup,
  SelectField,
  TextField,
  Toggle,
  DatePicker
} from 'redux-form-material-ui';
import IconButton from 'material-ui/IconButton';
import Input, { InputLabel, InputAdornment } from 'material-ui/Input';
import Visibility from 'material-ui-icons/Visibility';
import VisibilityOff from 'material-ui-icons/VisibilityOff';
import { withStyles } from 'material-ui/styles'
import { FormControl, FormHelperText } from 'material-ui/Form';
import MailOutline from 'material-ui-icons/MailOutline';

import NinesqTextField from './NinesqTextField';
import NinesqEmailField from './NinesqEmailField';
import NinesqPasswordField from './NinesqPasswordField';
import NineSqValidatorObj from './NineSqValidatorObj';

const styles = theme => ({
  flash_on: {
    display: 'block',
  },
  flash_off: {
    display: 'none',
  },
  fieldOutline: {
    fontWeight: 300,
    borderRadius: 2,
    border: '1px solid red',
    boxSizing: 'border-box',
  },
});

const messages = {
  "LocalLoginForm": {
    "email_placeholder": [
      "이메일 주소",
      "Email Address",
      "JP-Email Address",
      "ZH-Email Address",
      "FR-Email Address",
      "ES-Email Address"
    ],
    "email_label": [
      "이메일",
      "Email",
      "JP-Email",
      "ZH-Email",
      "FR-Email",
      "ES-Email"
    ],
    "password_label": [
      "패스워드",
      "Password",
      "JP-Password",
      "ZH-Password",
      "FR-Password",
      "ES-Password"
    ],
    "password_placeholder": [
      "패스워드",
      "Password",
      "JP-Password",
      "ZH-Password",
      "FR-Password",
      "ES-Password"
    ],
    "submit_label": [
      "로그인",
      "Log in",
      "JP-Log in",
      "ZH-Log in",
      "FR-Log in",
      "ES-Log in"
    ]
  }
};

export class LocalLoginForm extends Component {
  static propTypes = {
    classes: PropTypes.object.isRequired,
  }
  state = {
    showPassword: true,
    nsvalidator: new NineSqValidatorObj(this.props.dispatch, this.props.translate)
  }

  componentWillMount = () => {
    console.log("componentWillMount");
    this.props.dispatch(addTranslation(messages));
  }

  render() {
    const { handleSubmit, pristine, reset, submitting, translate, auth, classes } = this.props;

    return (
      <form onSubmit={handleSubmit}>
        <div className={classes.fieldOutline}>
          <Field name="myField"
            fullWidth
            validate={[this.state.nsvalidator.required]}
            component={NinesqEmailField}
            label={translate('LocalLoginForm.email_placeholder')}
            placeholder={translate('LocalLoginForm.email_placeholder')}
          />
        </div>
        <div className={classes.fieldOutline}>
          <Field name="mypassword"
            fullWidth
            validate={[this.state.nsvalidator.required]}
            component={NinesqPasswordField}
            placeholder={translate('LocalLoginForm.password_placeholder')}
            label={translate('LocalLoginForm.password_placeholder')}
          />
        </div>
        <div className={auth.error ? classes.flash_on: classes.flash_off}>{`error: ${auth.error}`}</div>

        <div><button type="submit">{translate('LocalLoginForm.submit_label')}</button></div>
      </form>
    )
  }
}

const mapStateToProps = (state, props) => ({
  auth: state.auth.auth,
  translate: getTranslate(state.locale),
  currentLanguage: getActiveLanguage(state.locale).code
});

const mapDispatchToProps = {

}

export default compose(reduxForm({form: 'localLogin'}), connect(mapStateToProps, mapDispatchToProps), withStyles(styles))(LocalLoginForm);

NineSqValidatorObj.js

import { getTranslate, getActiveLanguage, addTranslation } from 'react-localize-redux';

const messages = {
  "validate": {
    "required": [
      "필수항목",
      "Required",
      "JP-Required",
      "ZH-Required",
      "FR-Required",
      "ES-Required"
    ],
  }
};

class NineSqValidatorObj {
  constructor(dispatch, translate) {
    dispatch(addTranslation(messages));
    this.translate = translate;
    this.required = this.required.bind(this);
  }

  required(value){
    return (value ? undefined : this.translate('validate.required'));
  }

  maxLength(max) {
    return value => value && value.length > max ? `Must be ${max} characters or less` : undefined;
  }

  maxLengthEmail = this.maxLength(15);

  minLength(min) {
    return value =>
      value && value.length < min ? `Must be ${min} characters or more` : undefined;
  }

  minLength2 = this.minLength(2);

  number(value) {
    return value && isNaN(Number(value)) ? 'Must be a number' : undefined;
  }

  minValue(min) {
    return value => value && value < min ? `Must be at least ${min}` : undefined;
  }

  minValue18 = this.minValue(18);

  email(value) {
    return value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
      ? 'Invalid email address'
      : undefined;
  }

  alphaNumeric(value) {
    return value && /[^a-zA-Z0-9 ]/i.test(value)
    ? 'Only alphanumeric characters'
    : undefined;
  }

  phoneNumber(value) {
    return value && !/^(0|[1-9][0-9]{9})$/i.test(value)
      ? 'Invalid phone number, must be 10 digits'
      : undefined;
  }
}

export default NineSqValidatorObj;

I could try to implement it as HOC, however I thought it's wired to have render() in validator functions. So simply use it as a class. The current symptom is it won't find validate.required key.

If anyone can suggest how to implement this way, could you let me know?

aJoohongKim pushed a commit to krsoft/loopback-react-materialui-redux that referenced this issue Dec 26, 2017
@ryandrewjohnson
Copy link
Owner

Your validator function only really cares about what message it needs to display when value is invalid. My recommendation would be to avoid passing down the translate function, and instead provide the translated message to the validator. For example maybe each of your validation functions takes an additional param like so....

email(value, msg) {
    return value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
      ? msg
      : undefined;
  }

or if you want you could provide some sort of key value pair list of error messages from the component that has access to translate like so...

// from your connected component create a mapping of messages
const messages = {
  required: this.props.translate('validate.required'),
  email: this.props.translate('validate.email'),
  maxLength: this.props.translate('validate.maxLength'),
  ...
};

// pass those translated messages to your class
new NineSqValidatorObj(messages);

class NineSqValidatorObj {
  constructor(messages) {
     this.messages = messages;
  }

  // then use the messages from functions
  email(value, msg) {
    return value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)
      ? this.messages.email
       : undefined;
    }
}

I hope this helps.

@discrete
Copy link
Author

Thank you very much, @ryandrewjohnson. I've got your idea. You approach cleared my confusions. What I wanted was centralize error messages in one place. validator functions don't have to have the message at all. I will put all my error messages into a file and import it when I need it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants