Contents
Cài đặt App React
Đầu tiên, chúng ta sử dụng package create-react-app
để tạo app React. Sử dụng câu lệnh sau trong terminal.
npm install -g create-react-app
Chúng ta tạo app với tên react-password-strength, hoặc theo tên bạn muốn.
create-react-app react-password-strength
Tiếp theo, cần đặt đặt dependencies mà chúng ta cần cho app. Bằng câu lệnh sau
yarn add zxcvbn isemail prop-types node-sass bootstrap
Câu lệnh này sẽ cài những dependencies sau:
zxcvbn
– Thư viện kiểm tra độ mạnh yếu của mật khẩuisemail
– Thư viện kiểm tra định dạng emailprop-types
– xác thực đầu vào củacomponent
node-sass
– Được sử dụng để biên dịch các tệp Sass thành CSS.
Như bạn có thể nhận thấy, bạn đã cài đặt package bootstrap để có được một số style mặc định.
import 'bootstrap/dist/css/bootstrap.min.css';
Start app React bằng câu lệnh sau
yarn start
Sau khi app được start, và được đồng bộ với code. Khi có bất kì sự thay đổi nào của code, sẽ được đồng bộ ngay lập tức
Màn hình hiển thị trên browser

Xây dựng Components
Viết form với 3 input chính full name, email, và password. Và tạo message cho từng loại input riêng. Trong phần nàyn, bạn tạo các components trong React:
FormField
– Wraps a form input field with its attributes and change event handler.EmailField
– Wraps the emailFormField
and adds email validation logic to it.PasswordField
– Wraps the passwordFormField
and adds the password validation logic to it. Also attaches the password strength meter and some other visual cues to the field.JoinForm
– The fictitious Join Support Team form that houses the form fields.
Tạo thư mụccomponents
bên trong thư mục src của ứng dụng để chứa tất cả các components.
Component FormField
Tạo file FormField.js
trong src/components
và thêm đoạn code sau
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
class FormField extends Component {
// initialize state
state = { value: '', dirty: false, errors: [] }
hasChanged = e => {
e.preventDefault();
// destructure props - assign default dummy functions to validator and onStateChanged props
const { label, required = false, validator = f => f, onStateChanged = f => f } = this.props;
const value = e.target.value;
const isEmpty = value.length === 0;
const requiredMissing = this.state.dirty && required && isEmpty;
let errors = [];
if (requiredMissing) {
// if required and is empty, add required error to state
errors = [ ...errors, `${label} is required` ];
} else if ('function' === typeof validator) {
try {
validator(value);
} catch (e) {
// if validator throws error, add validation error to state
errors = [ ...errors, e.message ];
}
}
// update state and call the onStateChanged callback fn after the update
// dirty is only changed to true and remains true on and after the first state update
this.setState(({ dirty = false }) => ({ value, errors, dirty: !dirty || dirty }), () => onStateChanged(this.state));
}
render() {
const { value, dirty, errors } = this.state;
const { type, label, fieldId, placeholder, children } = this.props;
const hasErrors = errors.length > 0;
const controlClass = ['form-control', dirty ? hasErrors ? 'is-invalid' : 'is-valid' : '' ].join(' ').trim();
return (
<Fragment>
<div className="form-group px-3 pb-2">
<div className="d-flex flex-row justify-content-between align-items-center">
<label htmlFor={fieldId} className="control-label">{label}</label>
{/** Render the first error if there are any errors **/}
{ hasErrors && <div className="error form-hint font-weight-bold text-right m-0 mb-2">{ errors[0] }</div> }
</div>
{/** Render the children nodes passed to component **/}
{children}
<input type={type} className={controlClass} id={fieldId} placeholder={placeholder} value={value} onChange={this.hasChanged} />
</div>
</Fragment>
);
}
}
FormField.propTypes = {
type: PropTypes.oneOf(["text", "password"]).isRequired,
label: PropTypes.string.isRequired,
fieldId: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
required: PropTypes.bool,
children: PropTypes.node,
validator: PropTypes.func,
onStateChanged: PropTypes.func
};
export default FormField;
Input State: Trước tiên, bạn đã khởi tạo state
cho field component để theo dõi giá trị hiện tại của input, trạng thái dirty
và mọi lỗi hiện có.
Handle Input Change: Tiếp theo, bạn đã thêm trình xử lý sự kiện hasChanged (e) để cập nhật giá trị state thành giá trị đầu input hiện tại trên mỗi thay đổi đối với input React
Rendering and Props: Ở đây bạn đang hiển thị filed input và label của nó. Bạn cũng có điều kiện đưa ra lỗi đầu tiên trong mảng state lỗi (nếu có bất kỳ lỗi nào).
Component EmailField
Tạo file EmailField.js
trong thư mục src/components
và thêm đoạn code sau
import React from 'react';
import PropTypes from 'prop-types';
import { validate } from 'isemail';
import FormField from './FormField';
const EmailField = props => {
// prevent passing type and validator props from this component to the rendered form field component
const { type, validator, ...restProps } = props;
// validateEmail function using the validate() method of the isemail package
const validateEmail = value => {
if (!validate(value)) throw new Error('Email is invalid');
};
// pass the validateEmail to the validator prop
return <FormField type="text" validator={validateEmail} {...restProps} />
};
EmailField.propTypes = {
label: PropTypes.string.isRequired,
fieldId: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
required: PropTypes.bool,
children: PropTypes.node,
onStateChanged: PropTypes.func
};
export default EmailField;
Trong component EmailField
, bạn đang render component FormField
và truyền hàm email validation tới prop validator
. Bạn sử dụng validate()
của package isemail
để validate email.
Component PasswordField
Tạo file PasswordField.js
trong thư mục src/components
và thêm đoạn code sau
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import zxcvbn from 'zxcvbn';
import FormField from './FormField';
class PasswordField extends Component {
constructor(props) {
super(props);
const { minStrength = 3, thresholdLength = 7 } = props;
// set default minStrength to 3 if not a number or not specified
// minStrength must be a a number between 0 - 4
this.minStrength = typeof minStrength === 'number'
? Math.max( Math.min(minStrength, 4), 0 )
: 3;
// set default thresholdLength to 7 if not a number or not specified
// thresholdLength must be a minimum value of 7
this.thresholdLength = typeof thresholdLength === 'number'
? Math.max(thresholdLength, 7)
: 7;
// initialize internal component state
this.state = { password: '', strength: 0 };
};
stateChanged = state => {
// update the internal state using the updated state from the form field
this.setState({
password: state.value,
strength: zxcvbn(state.value).score
}, () => this.props.onStateChanged(state));
};
validatePasswordStrong = value => {
// ensure password is long enough
if (value.length <= this.thresholdLength) throw new Error("Password is short");
// ensure password is strong enough using the zxcvbn library
if (zxcvbn(value).score < this.minStrength) throw new Error("Password is weak");
};
render() {
const { type, validator, onStateChanged, children, ...restProps } = this.props;
const { password, strength } = this.state;
const passwordLength = password.length;
const passwordStrong = strength >= this.minStrength;
const passwordLong = passwordLength > this.thresholdLength;
// dynamically set the password length counter class
const counterClass = ['badge badge-pill', passwordLong ? passwordStrong ? 'badge-success' : 'badge-warning' : 'badge-danger'].join(' ').trim();
// password strength meter is only visible when password is not empty
const strengthClass = ['strength-meter mt-2', passwordLength > 0 ? 'visible' : 'invisible'].join(' ').trim();
return (
<Fragment>
<div className="position-relative">
{/** Pass the validation and stateChanged functions as props to the form field **/}
<FormField type="password" validator={this.validatePasswordStrong} onStateChanged={this.stateChanged} {...restProps}>
<span className="d-block form-hint">To conform with our Strong Password policy, you are required to use a sufficiently strong password. Password must be more than 7 characters.</span>
{children}
{/** Render the password strength meter **/}
<div className={strengthClass}>
<div className="strength-meter-fill" data-strength={strength}></div>
</div>
</FormField>
<div className="position-absolute password-count mx-3">
{/** Render the password length counter indicator **/}
<span className={counterClass}>{ passwordLength ? passwordLong ? `${this.thresholdLength}+` : passwordLength : '' }</span>
</div>
</div>
</Fragment>
);
}
}
PasswordField.propTypes = {
label: PropTypes.string.isRequired,
fieldId: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
required: PropTypes.bool,
children: PropTypes.node,
onStateChanged: PropTypes.func,
minStrength: PropTypes.number,
thresholdLength: PropTypes.number
};
export default PasswordField;
Component này đang sử dụng packagezxcvbn
để ước tính độ mạnh của password. Package có zxcvbn() lấy chuỗi mật khẩu làm đối số đầu tiên của nó và trả về một đối tượng có một số thuộc tính để ước tính cường độ mật khẩu.
Đây lànhững gì đang xảy ra trong component PasswordField:
Initialization: Trong constructor(), bạn đã tạo hai thuộc tính, thresholdLangth
và minStrength, từ chỗ dựa tương ứng của chúng được truyền cho component. thresholdLength
là độ dài mật khẩu tối thiểu trước khi nó có thể được coi là đủ dài. Mặc định là 7. minStrength
là điểm thấp nhất zxcvbn
trước khi mật khẩu được coi là đủ mạnh. Giá trị của nó nằm trong khoảng từ 0-4. Nó mặc định là 3 nếu không được custom
Handling Password Changes: Bạn đã xác định function validation password sẽ được chuyển đến prop trình xác thực của component FormField bên dưới. Hàm đảm bảo rằng độ dài mật khẩu dài hơn thresholdLength
và cũng có điểm zxcvbn() tối thiểu được chỉ định.
Rendering and Props: Tại đây, bạn đã render component FormField bên dưới cùng với một số component cho input đầu vào, máy đo mật khẩu và bộ đếm độ dài mật khẩu.
Đồng hồ đo cường độ mật khẩu cho biết độ mạnh của mật khẩu hiện tại dựa trên statei và được định cấu hình ở chế độ ẩn dưới dạng động nếu độ dài mật khẩu là 0. Đồng hồ sẽ chỉ ra các màu khác nhau cho các mức cường độ khác nhau.
Thành phần PasswordField chấp nhận hai fields, minStrength
và thresholdLength, như được định nghĩa trong component’s propTypes.
Component Joinform
Tạo file JoinForm.js
trong thư mục src/components
import React, { Component } from 'react';
import FormField from './FormField';
import EmailField from './EmailField';
import PasswordField from './PasswordField';
class JoinForm extends Component {
// initialize state to hold validity of form fields
state = { fullname: false, email: false, password: false }
// higher-order function that returns a state change watch function
// sets the corresponding state property to true if the form field has no errors
fieldStateChanged = field => state => this.setState({ [field]: state.errors.length === 0 });
// state change watch functions for each field
emailChanged = this.fieldStateChanged('email');
fullnameChanged = this.fieldStateChanged('fullname');
passwordChanged = this.fieldStateChanged('password');
render() {
const { fullname, email, password } = this.state;
const formValidated = fullname && email && password;
// validation function for the fullname
// ensures that fullname contains at least two names separated with a space
const validateFullname = value => {
const regex = /^[a-z]{2,}(\s[a-z]{2,})+$/i;
if (!regex.test(value)) throw new Error('Fullname is invalid');
};
return (
<div className="form-container d-table-cell position-relative align-middle">
<form action="/" method="POST" noValidate>
<div className="d-flex flex-row justify-content-between align-items-center px-3 mb-5">
<legend className="form-label mb-0">Support Team</legend>
{/** Show the form button only if all fields are valid **/}
{ formValidated && <button type="button" className="btn btn-primary text-uppercase px-3 py-2">Join</button> }
</div>
<div className="py-5 border-gray border-top border-bottom">
{/** Render the fullname form field passing the name validation fn **/}
<FormField type="text" fieldId="fullname" label="Full Name" placeholder="Enter Full Name" validator={validateFullname} onStateChanged={this.fullnameChanged} required />
{/** Render the email field component **/}
<EmailField fieldId="email" label="Email" placeholder="Enter Email Address" onStateChanged={this.emailChanged} required />
{/** Render the password field component using thresholdLength of 7 and minStrength of 3 **/}
<PasswordField fieldId="password" label="Password" placeholder="Enter Password" onStateChanged={this.passwordChanged} thresholdLength={7} minStrength={3} required />
</div>
</form>
</div>
);
}
}
export default JoinForm;
Component JoinForm bao bọc các components field form tạo nên form. Chúng ta đã khởi tạo state để giữ tính hợp lệ của ba fields: fullname, email và password. Tất cả đều false hoặc invalid.
Cuối cùng, form đượcrendered. Lưu ý rằng bạn đã thêm function validation vào field fullname
để đảm bảo rằng ít nhất có tên và họ, được phân tách bằng khoảng trắng và chỉ chứa các ký tự bảng chữ cái.
Component trong App
Cho đến thời điểm này, trình duyệt vẫn hiển thị ứng dụng React soạn sẵn. Bây giờ bạn sẽ sửa đổi file App.js trong thư mục src để hiển thị JoinForm
bên trong AppComponent.
Thêm đoạn code sau
import React from 'react';
import JoinForm from './components/JoinForm';
import './App.css';
function App() {
return (
<div className="main-container d-table position-absolute m-auto">
<JoinForm />
</div>
);
}
export default App;
Styling với Sass
Bạn chỉ còn một bước nữa là hoàn thành ứng dụng của mình. Hiện tại, mọi thứ có vẻ chưa chặt chẽ. Trong bước này, bạn sẽ tiếp tục và define một số style cho form.
Để tận dụng sức mạnh của các biến Sass, cấu trúc lồng(nesting) và vòng lặp, trước đây chúng ta đã cài đặt dependency của node-sass. Bạn đang sử dụng Sass để tạo tệp CSS mà trình duyệt có thể hiểu được.
Có hai điều bạn sẽ cần thay đổi để sử dụng Sass trong ứng dụng của mình
- Đổi tên file
src/App.css
thànhsrc/App.scss
. - Chỉnh đoạn import trong file
src/App.js
Sau khi đổi tên tệp src/App.css, hãy cập nhật file src/App.js của bạn như sau
import './App.scss';
Tiếp theo, thay thế code hiện có trong file App.scss bằng code sau để định dạng app React
/** Declare some variables **/
$primary: #007bff;
// Password strength meter color for the different levels
$strength-colors: (darkred, orangered, orange, yellowgreen, green);
// Gap width between strength meter bars
$strength-gap: 6px;
body {
font-size: 62.5%;
}
.main-container {
width: 400px;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.form-container {
bottom: 100px;
}
legend.form-label {
font-size: 1.5rem;
color: desaturate(darken($primary, 10%), 60%);
}
.control-label {
font-size: 0.8rem;
font-weight: bold;
color: desaturate(darken($primary, 10%), 80%);
}
.form-control {
font-size: 1rem;
}
.form-hint {
font-size: 0.6rem;
line-height: 1.4;
margin: -5px auto 5px;
color: #999;
&.error {
color: #C00;
font-size: 0.8rem;
}
}
button.btn {
letter-spacing: 1px;
font-size: 0.8rem;
font-weight: 600;
}
.password-count {
bottom: 16px;
right: 10px;
font-size: 1rem;
}
.strength-meter {
position: relative;
height: 3px;
background: #DDD;
margin: 7px 0;
border-radius: 2px;
// Dynamically create the gap effect
&:before,
&:after {
content: '';
height: inherit;
background: transparent;
display: block;
border-color: #FFF;
border-style: solid;
border-width: 0 $strength-gap 0;
position: absolute;
width: calc(20% + #{$strength-gap});
z-index: 10;
}
// Dynamically create the gap effect
&:before {
left: calc(20% - #{($strength-gap / 2)});
}
// Dynamically create the gap effect
&:after {
right: calc(20% - #{($strength-gap / 2)});
}
}
.strength-meter-fill {
background: transparent;
height: inherit;
position: absolute;
width: 0;
border-radius: inherit;
transition: width 0.5s ease-in-out, background 0.25s;
// Dynamically generate strength meter color styles
@for $i from 1 through 5 {
&[data-strength='#{$i - 1}'] {
width: (20% * $i);
background: nth($strength-colors, $i);
}
}
}
Bạn đã thành công trong việc thêm các style theo ứng dụng của bạn. Lưu ý việc sử dụng nội dung CSS được tạo trong .strength-meter:before và .strength-meter:before các phần tử để thêm các khoảng trống vào máy đo cường độ mật khẩu.
Bạn cũng có thể sử dụng Sass @for
để tự động tạo màu cho máy đo cường độ ở các mức cường độ mật khẩu khác nhau.
Đây là màn hình hoàn chỉnh:

Hiển thị messages khi báo lỗi nhìn như sau

Trong trường hợp không có lỗi, và độ mạnh cao

Kết luận
Qua bài viết chúng ta có thể kiểm tra độ mạnh yếu password của app React. Từ đó có thể thêm vào app để có trải nghiệm tốt hơn cho người dùng
Tham khảo về React : Bí quyết lập trình React cho người mới bắt đầu

Bài viết này được sưu tầm và tổng hợp từ nhiều nguồn trên Internet.
Nếu có gì không hiểu thì inbox messenger bên dưới mình hỗ trợ thêm nhé.