Membuat Pustaka Validasi React Anda Sendiri: Fitur (Bagian 2)
Diterbitkan: 2022-03-10Menerapkan perpustakaan validasi tidak terlalu sulit. Juga tidak menambahkan semua fitur tambahan yang membuat perpustakaan validasi Anda jauh lebih baik daripada yang lain.
Artikel ini akan melanjutkan implementasi library validasi yang kami mulai terapkan di bagian sebelumnya dari seri artikel ini. Ini adalah fitur yang akan membawa kita dari bukti konsep sederhana ke perpustakaan yang sebenarnya dapat digunakan!
- Bagian 1: Dasar-dasar
- Bagian 2: Fitur
- Bagian 3: Pengalaman
Hanya Tampilkan Validasi Saat Dikirim
Karena kami memvalidasi semua peristiwa perubahan, kami menampilkan pesan kesalahan pengguna terlalu dini untuk pengalaman pengguna yang baik. Ada beberapa cara yang bisa kita lakukan untuk menguranginya.
Solusi pertama cukup menyediakan flag yang submitted
sebagai properti yang dikembalikan dari hook useValidation
. Dengan cara ini, kita dapat memeriksa apakah formulir dikirimkan atau tidak sebelum menampilkan pesan kesalahan. Kelemahannya di sini adalah "tampilkan kode kesalahan" kami menjadi sedikit lebih lama:
<label> Username <br /> <input {...getFieldProps('username')} /> {submitted && errors.username && ( <div className="error">{errors.username}</div> )} </label>
Pendekatan lain adalah dengan memberikan kumpulan kesalahan kedua (sebut saja mereka submittedErrors
), yang merupakan objek kosong jika yang submitted
salah, dan objek errors
jika itu benar. Kita bisa mengimplementasikannya seperti ini:
const useValidation = config => { // as before return { errors: state.errors, submittedErrors: state.submitted ? state.errors : {}, }; }
Dengan cara ini, kita dapat dengan mudah merusak jenis kesalahan yang ingin kita tampilkan. Kami tentu saja dapat melakukan ini di situs panggilan — tetapi dengan menyediakannya di sini, kami menerapkannya sekali, bukan di dalam semua konsumen.
- Lihat demo CodeSandbox yang menunjukkan bagaimana
submittedErrors
dapat digunakan.
Tampilkan Pesan Kesalahan On-Blur
Banyak orang ingin ditampilkan kesalahan begitu mereka meninggalkan bidang tertentu. Kami dapat menambahkan dukungan untuk ini, dengan melacak bidang mana yang telah "diburamkan" (diavigasi menjauh dari), dan mengembalikan objek blurredErrors
, mirip dengan submittedErrors
di atas.
Implementasinya mengharuskan kita untuk menangani jenis tindakan baru — blur
, yang akan memperbarui objek status baru yang disebut blurred
:
const initialState = { values: {}, errors: {}, blurred: {}, submitted: false, }; function validationReducer(state, action) { switch (action.type) { // as before case 'blur': const blurred = { ...state.blurred, [action.payload]: true }; return { ...state, blurred }; default: throw new Error('Unknown action type'); } }
Saat kami mengirimkan tindakan blur
, kami membuat properti baru di objek status blurred
dengan nama bidang sebagai kunci, yang menunjukkan bahwa bidang itu telah diburamkan.
Langkah selanjutnya adalah menambahkan prop onBlur
ke fungsi getFieldProps
kami, yang mengirimkan tindakan ini jika berlaku:
getFieldProps: fieldName => ({ // as before onBlur: () => { dispatch({ type: 'blur', payload: fieldName }); }, }),
Terakhir, kita perlu menyediakan blurredErrors
dari kait useValidation
kita sehingga kita dapat menampilkan kesalahan hanya saat dibutuhkan.
const blurredErrors = useMemo(() => { const returnValue = {}; for (let fieldName in state.errors) { returnValue[fieldName] = state.blurred[fieldName] ? state.errors[fieldName] : null; } return returnValue; }, [state.errors, state.blurred]); return { // as before blurredErrors, };
Di sini, kami membuat fungsi memo yang mengetahui kesalahan mana yang akan ditampilkan berdasarkan apakah bidang telah diburamkan atau tidak. Kami menghitung ulang kumpulan kesalahan ini setiap kali kesalahan atau objek kabur berubah. Anda dapat membaca lebih lanjut tentang kait useMemo
di dokumentasi.
- Lihat demo CodeSandbox
Saatnya Untuk Refactor Kecil
Komponen useValidation
kami sekarang mengembalikan tiga set kesalahan — sebagian besar akan terlihat sama di beberapa titik waktu. Alih-alih mengikuti rute ini, kita akan membiarkan pengguna menentukan dalam konfigurasi ketika mereka ingin kesalahan dalam formulir mereka muncul.
Opsi baru kami — showErrors
— akan menerima "kirim" (default), "selalu" atau "blur". Kami dapat menambahkan lebih banyak opsi nanti, jika perlu.
function getErrors(state, config) { if (config.showErrors === 'always') { return state.errors; } if (config.showErrors === 'blur') { return Object.entries(state.blurred) .filter(([, blurred]) => blurred) .reduce((acc, [name]) => ({ ...acc, [name]: state.errors[name] }), {}); } return state.submitted ? state.errors : {}; } const useValidation = config => { // as before const errors = useMemo( () => getErrors(state, config), [state, config] ); return { errors, // as before }; };
Karena kode penanganan kesalahan mulai mengambil sebagian besar ruang kami, kami memfaktorkannya kembali ke fungsinya sendiri. Jika Anda tidak mengikuti Object.entries
dan .reduce
stuff — tidak apa-apa — itu adalah penulisan ulang kode for...in
di bagian terakhir.
Jika kita membutuhkan validasi onBlur atau instan, kita bisa menentukan prop showError
di objek konfigurasi useValidation
kita.
const config = { // as before showErrors: 'blur', }; const { getFormProps, getFieldProps, errors } = useValidation(config); // errors would now only include the ones that have been blurred
- Lihat demo CodeSandbox
Catatan Tentang Asumsi
“Perhatikan bahwa saya sekarang mengasumsikan bahwa setiap formulir ingin menampilkan kesalahan dengan cara yang sama (selalu dikirim, selalu buram, dll). Itu mungkin benar untuk sebagian besar aplikasi, tetapi mungkin tidak untuk semua. Menyadari asumsi Anda adalah bagian besar dari pembuatan API Anda.”
Izinkan untuk Validasi Silang
Fitur yang sangat kuat dari pustaka validasi adalah memungkinkan validasi silang — yaitu, untuk mendasarkan validasi satu bidang pada nilai bidang lain.
Untuk mengizinkan ini, kita perlu membuat kait kustom kita menerima fungsi alih-alih objek. Fungsi ini akan dipanggil dengan nilai bidang saat ini. Menerapkannya sebenarnya hanya tiga baris kode!
function useValidation(config) { const [state, dispatch] = useReducer(...); if (typeof config === 'function') { config = config(state.values); } }
Untuk menggunakan fitur ini, kita cukup melewatkan fungsi yang mengembalikan objek konfigurasi ke useValidation
:
const { getFieldProps } = useValidation(fields => ({ password: { isRequired: { message: 'Please fill out the password' }, }, repeatPassword: { isRequired: { message: 'Please fill out the password one more time' }, isEqual: { value: fields.password, message: 'Your passwords don\'t match' } } }));
Di sini, kami menggunakan nilai fields.password
untuk memastikan dua bidang kata sandi berisi input yang sama (yang merupakan pengalaman pengguna yang buruk, tapi itu untuk posting blog lain).
- Lihat demo CodeSandbox yang tidak membiarkan nama pengguna dan kata sandi bernilai sama.
Tambahkan Beberapa Kemenangan Aksesibilitas
Hal yang rapi untuk dilakukan ketika Anda bertanggung jawab atas properti dari suatu bidang adalah menambahkan aria-tag yang benar secara default. Ini akan membantu pembaca layar menjelaskan formulir Anda.
Peningkatan yang sangat sederhana adalah menambahkan aria-invalid="true"
jika bidang memiliki kesalahan. Mari kita terapkan itu:
const useValidation = config => { // as before return { // as before getFieldProps: fieldName => ({ // as before 'aria-invalid': String(!!errors[fieldName]), }), } };
Itu satu baris kode tambahan, dan pengalaman pengguna yang jauh lebih baik untuk pengguna pembaca layar.
Anda mungkin bertanya-tanya mengapa kami menulis String(!!state.errors[fieldName])
? state.errors[fieldName]
adalah string, dan operator negasi ganda memberi kita nilai boolean (dan bukan hanya nilai benar atau salah). Namun, properti aria-invalid
harus berupa string (itu juga dapat membaca "tata bahasa" atau "ejaan", selain "benar" atau "salah"), jadi kita perlu memaksa boolean itu menjadi setara string.
Masih ada beberapa penyesuaian lagi yang bisa kami lakukan untuk meningkatkan aksesibilitas, tetapi ini sepertinya awal yang adil.
Sintaks Pesan Validasi Singkatan
Sebagian besar validator dalam paket calidators
(dan sebagian besar validator lainnya, saya asumsikan) hanya memerlukan pesan kesalahan. Bukankah lebih baik jika kita bisa meneruskan string itu alih-alih objek dengan properti message
yang berisi string itu?
Mari kita terapkan itu dalam fungsi validateField
kami:
function validateField(fieldValue = '', fieldConfig, allFieldValues) { for (let validatorName in fieldConfig) { let validatorConfig = fieldConfig[validatorName]; if (typeof validatorConfig === 'string') { validatorConfig = { message: validatorConfig }; } const configuredValidator = validators[validatorName](validatorConfig); const errorMessage = configuredValidator(fieldValue); if (errorMessage) { return errorMessage; } } return null; }
Dengan cara ini, kita dapat menulis ulang konfigurasi validasi kita seperti ini:
const config = { username: { isRequired: 'The username is required', isEmail: 'The username should be a valid email address', }, };
Jauh lebih bersih!
Nilai Bidang Awal
Terkadang, kita perlu memvalidasi formulir yang sudah diisi. Kait khusus kami belum mendukung itu — jadi mari kita mulai!
Nilai bidang awal akan ditentukan dalam konfigurasi untuk setiap bidang, di properti initialValue
. Jika tidak ditentukan, defaultnya adalah string kosong.
Kami akan membuat fungsi getInitialState
, yang akan membuat status awal peredam kami untuk kami.
function getInitialState(config) { if (typeof config === 'function') { config = config({}); } const initialValues = {}; const initialBlurred = {}; for (let fieldName in config.fields) { initialValues[fieldName] = config.fields[fieldName].initialValue || ''; initialBlurred[fieldName] = false; } const initialErrors = validateFields(initialValues, config.fields); return { values: initialValues, errors: initialErrors, blurred: initialBlurred, submitted: false, }; }
Kami memeriksa semua bidang, memeriksa apakah mereka memiliki properti initialValue
, dan menetapkan nilai awal yang sesuai. Kemudian kami menjalankan nilai awal tersebut melalui validator dan menghitung kesalahan awal juga. Kami mengembalikan objek status awal, yang kemudian dapat diteruskan ke kait useReducer
kami.
Karena kami memperkenalkan prop non-validator ke dalam konfigurasi bidang, kami harus melewatinya saat memvalidasi bidang kami. Untuk melakukan itu, kami mengubah fungsi validateField
kami:
function validateField(fieldValue = '', fieldConfig) { const specialProps = ['initialValue']; for (let validatorName in fieldConfig) { if (specialProps.includes(validatorName)) { continue; } // as before } }
Saat kami terus menambahkan lebih banyak fitur seperti ini, kami dapat menambahkannya ke array specialProps
kami.
- Lihat demo CodeSandbox
Menyimpulkan
Kami sedang dalam perjalanan untuk membuat perpustakaan validasi yang luar biasa. Kami telah menambahkan banyak fitur, dan sekarang kami adalah pemimpin yang cukup banyak berpikir.
Di bagian selanjutnya dari seri ini, kami akan menambahkan semua tambahan yang membuat pustaka validasi kami menjadi tren di LinkedIn.