CustomInput.jsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import React, { Component } from "react";
  2. import PropTypes from "prop-types";
  3. import reactTriggerChange from "react-trigger-change";
  4. const regex = {
  5. azAZ09_: /^[A-Za-z0-9_]+$/,
  6. az09_: /^[a-z0-9_]+$/,
  7. emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
  8. password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]/,
  9. ascii: /^[\x00-\x7F]+$/,
  10. };
  11. const isLength = (string, min, max) => {
  12. return !(typeof string !== "string" || string.length < min || string.length > max);
  13. };
  14. const validation = {
  15. username: (value) => {
  16. const errors = [];
  17. if (!isLength(value, 2, 32)) errors.push("Username must be between 2 and 32 characters long.");
  18. if (!regex.azAZ09_.test(value)) errors.push("Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.");
  19. return errors;
  20. },
  21. email: (value) => {
  22. const errors = [];
  23. if (!isLength(value, 3, 254)) errors.push("Email must be between 3 and 254 characters long.");
  24. if (value.indexOf("@") !== value.lastIndexOf("@") || !regex.emailSimple.test(value)) errors.push("Invalid email format.");
  25. return errors;
  26. },
  27. password: (value) => {
  28. const errors = [];
  29. if (!isLength(value, 6, 200)) errors.push("Password must be between 6 and 200 characters long.");
  30. if (!regex.password.test(value)) errors.push("Invalid password format.");
  31. return errors;
  32. },
  33. };
  34. export default class CustomInput extends Component {
  35. static propTypes = {
  36. type: PropTypes.string,
  37. inputType: PropTypes.string,
  38. name: PropTypes.string,
  39. value: PropTypes.string,
  40. label: PropTypes.string,
  41. placeholder: PropTypes.string,
  42. customInputEvents: PropTypes.object,
  43. validationCallback: PropTypes.func,
  44. onRef: PropTypes.func,
  45. };
  46. static defaultProps = {
  47. type: "text",
  48. inputType: "text",
  49. name: "",
  50. value: "",
  51. label: "",
  52. placeholder: "",
  53. customInputEvents: {},
  54. validationCallback: () => {},
  55. onRef: () => {},
  56. };
  57. static validationCallback = (ctx) => {
  58. return (name, invalid) => {
  59. const inputInvalid = ctx.state.inputInvalid;
  60. inputInvalid[name] = invalid;
  61. ctx.setState({ inputInvalid });
  62. };
  63. };
  64. static hasInvalidInput = (inputInvalid, properties) => {
  65. let invalid = false;
  66. if (properties) {
  67. properties.forEach((property) => {
  68. if (inputInvalid[property]) invalid = true;
  69. });
  70. } else {
  71. Object.keys((key) => {
  72. if (key) invalid = true;
  73. });
  74. }
  75. return invalid;
  76. };
  77. constructor(props) {
  78. super(props);
  79. this.state = {
  80. customInputEvents: props.customInputEvents,
  81. errors: "",
  82. value: props.value,
  83. validateOnChange: false,
  84. };
  85. if (this.state.customInputEvents.onBlur) {
  86. const oldOnBlur = this.state.customInputEvents.onBlur;
  87. this.state.customInputEvents.onBlur = () => {
  88. this.onBlurHandler();
  89. oldOnBlur();
  90. };
  91. } else this.state.customInputEvents.onBlur = this.onBlurHandler;
  92. if (this.state.customInputEvents.onChange) {
  93. const oldOnChange = this.state.customInputEvents.onChange;
  94. this.state.customInputEvents.onChange = (event) => {
  95. this.onChangeHandler(event);
  96. oldOnChange(event);
  97. };
  98. } else this.state.customInputEvents.onChange = this.onChangeHandler;
  99. }
  100. componentDidMount() {
  101. this.props.onRef(this);
  102. }
  103. componentWillUnmount() {
  104. this.props.onRef(null);
  105. }
  106. // Triggered when user stops focusing on the input element
  107. onBlurHandler = () => {
  108. this.validateInput();
  109. };
  110. // Triggered when the input element's value changes
  111. onChangeHandler = (event) => {
  112. this.setState({
  113. value: event.target.value,
  114. }, () => {
  115. if (this.state.validateOnChange === true) {
  116. this.setState({
  117. validateOnChange: false,
  118. });
  119. this.validateInput();
  120. }
  121. });
  122. };
  123. listErrors = () => {
  124. let errors = this.state.errors;
  125. let key = 0;
  126. if (errors.length > 0) {
  127. errors = errors.map((error) => {
  128. key++;
  129. return (<li key={ key }>{ error }</li>);
  130. });
  131. return (
  132. <ul className="validation-errors">
  133. { errors }
  134. </ul>
  135. );
  136. } return "";
  137. };
  138. validateInput = () => {
  139. const value = this.state.value;
  140. const type = this.props.type;
  141. const errors = (validation[type]) ? validation[type](value) : [];
  142. this.setState({ errors });
  143. this.props.validationCallback(this.props.name, errors.length > 0);
  144. };
  145. triggerChangeEvent = (validateOnChange) => {
  146. reactTriggerChange(this.inputElement);
  147. this.setState({ validateOnChange });
  148. };
  149. render() {
  150. return (
  151. <label htmlFor={ this.props.name }>
  152. <span>{ this.props.label }</span>
  153. <input
  154. placeholder={ this.props.placeholder }
  155. type={ this.props.inputType }
  156. name={ this.props.name }
  157. value={ this.props.value }
  158. className={ (this.state.errors.length > 0) ? "has-validation-errors" : "" }
  159. { ...this.state.customInputEvents }
  160. ref={ (input) => this.inputElement = input }
  161. />
  162. { this.listErrors() }
  163. </label>
  164. );
  165. }
  166. }