Last active
July 7, 2019 14:47
-
-
Save llauderesv/b85abb78c6960c97931d8cb20741e542 to your computer and use it in GitHub Desktop.
React Form validation using React Hooks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import Form from './Form'; | |
import logo from './logo.svg'; | |
import './App.css'; | |
function App() { | |
return ( | |
<div className="App"> | |
<div className="App-header"> | |
<img className="App-logo" src={logo} alt="react-logo" /> | |
<p>React Form Validation using React Hooks.</p> | |
</div> | |
<Form /> | |
</div> | |
); | |
} | |
export default App; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import useForm from './useForm'; | |
function Form() { | |
// Define your state schema | |
const stateSchema = { | |
fname: { value: '', error: '' }, | |
lname: { value: '', error: '' }, | |
tags: { value: '', error: '' }, | |
}; | |
// Define your validationStateSchema | |
// Note: validationStateSchema and stateSchema property | |
// should be the same in-order validation works! | |
const validationStateSchema = { | |
fname: { | |
required: true, | |
validator: { | |
regEx: /^[a-zA-Z]+$/, | |
error: 'Invalid first name format.', | |
}, | |
}, | |
lname: { | |
required: true, | |
validator: { | |
regEx: /^[a-zA-Z]+$/, | |
error: 'Invalid last name format.', | |
}, | |
}, | |
tags: { | |
required: true, | |
validator: { | |
regEx: /^(,?\w{3,})+$/, | |
error: 'Invalid tag format.', | |
}, | |
}, | |
}; | |
function onSubmitForm(state) { | |
alert(JSON.stringify(state, null, 2)); | |
} | |
const { state, handleOnChange, handleOnSubmit, disable } = useForm( | |
stateSchema, | |
validationStateSchema, | |
onSubmitForm | |
); | |
const errorStyle = { | |
color: 'red', | |
fontSize: '13px', | |
}; | |
return ( | |
<div> | |
<form onSubmit={handleOnSubmit}> | |
<div> | |
<label htmlFor="fname"> | |
First name: | |
<input | |
type="text" | |
name="fname" | |
value={state.fname.value} | |
onChange={handleOnChange} | |
/> | |
</label> | |
{state.fname.error && <p style={errorStyle}>{state.fname.error}</p>} | |
</div> | |
<div> | |
<label htmlFor="lname"> | |
Last name: | |
<input | |
type="text" | |
name="lname" | |
value={state.lname.value} | |
onChange={handleOnChange} | |
/> | |
</label> | |
{state.lname.error && <p style={errorStyle}>{state.lname.error}</p>} | |
</div> | |
<div> | |
<label htmlFor="tags"> | |
Tags: | |
<input | |
type="text" | |
name="tags" | |
value={state.tags.value} | |
onChange={handleOnChange} | |
/> | |
</label> | |
{state.tags.error && <p style={errorStyle}>{state.tags.error}</p>} | |
</div> | |
<input type="submit" name="submit" disabled={disable} /> | |
</form> | |
</div> | |
); | |
} | |
export default Form; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useEffect, useCallback } from 'react'; | |
function useForm(stateSchema, validationSchema = {}, callback) { | |
const [state, setState] = useState(stateSchema); | |
const [disable, setDisable] = useState(true); | |
const [isDirty, setIsDirty] = useState(false); | |
// Disable button in initial render. | |
useEffect(() => { | |
setDisable(true); | |
}, []); | |
// For every changed in our state this will be fired | |
// To be able to disable the button | |
useEffect(() => { | |
if (isDirty) { | |
setDisable(validateState()); | |
} | |
}, [state, isDirty]); | |
// Used to disable submit button if there's an error in state | |
// or the required field in state has no value. | |
// Wrapped in useCallback to cached the function to avoid intensive memory leaked | |
// in every re-render in component | |
const validateState = useCallback(() => { | |
const hasErrorInState = Object.keys(validationSchema).some(key => { | |
const isInputFieldRequired = validationSchema[key].required; | |
const stateValue = state[key].value; // state value | |
const stateError = state[key].error; // state error | |
return (isInputFieldRequired && !stateValue) || stateError; | |
}); | |
return hasErrorInState; | |
}, [state, validationSchema]); | |
// Used to handle every changes in every input | |
const handleOnChange = useCallback( | |
event => { | |
setIsDirty(true); | |
const name = event.target.name; | |
const value = event.target.value; | |
let error = ''; | |
if (validationSchema[name].required) { | |
if (!value) { | |
error = 'This is required field.'; | |
} | |
} | |
if ( | |
validationSchema[name].validator !== null && | |
typeof validationSchema[name].validator === 'object' | |
) { | |
if (value && !validationSchema[name].validator.regEx.test(value)) { | |
error = validationSchema[name].validator.error; | |
} | |
} | |
setState(prevState => ({ | |
...prevState, | |
[name]: { value, error }, | |
})); | |
}, | |
[validationSchema] | |
); | |
const handleOnSubmit = useCallback( | |
event => { | |
event.preventDefault(); | |
// Make sure that validateState returns false | |
// Before calling the submit callback function | |
if (!validateState()) { | |
callback(state); | |
} | |
}, | |
[state] | |
); | |
return { state, disable, handleOnChange, handleOnSubmit }; | |
} | |
export default useForm; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment