Skip to content

Instantly share code, notes, and snippets.

@NickFoden
Last active February 27, 2020 13:00
Show Gist options
  • Save NickFoden/42fd969500459fe511efd116175b8620 to your computer and use it in GitHub Desktop.
Save NickFoden/42fd969500459fe511efd116175b8620 to your computer and use it in GitHub Desktop.
React-Hook-Form
// https://react-hook-form.com/get-started
import React from 'react'
import { useForm } from 'react-hook-form'
// The following component is an example of your existing Input Component
const Input = ({ label, register, required }) => (
<>
<label>{label}</label>
<input name={label} ref={register({ required })} />
</>
)
// you can use React.forwardRef to pass the ref too
const Select = React.forwardRef(({ label, register }, ref) => (
<>
<label>{label}</label>
<select name={label} ref={ref}>
<option value="20">20</option>
<option value="30">30</option>
</select>
</>
))
export default function App() {
const { register, handleSubmit } = useForm()
const onSubmit = data => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input label="First Name" ref={register} required />
<Select label="Age" ref={register} />
<input type="submit" />
</form>
)
}
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { ErrorDiv, Input, Label, wrapperCss } from './InputBox.styles';
const InputBox = props => {
const { errorMsg, errorObj, id, label, name, maxLength, onChange, placeHolderText, testId, type, validate, ...rest } = props;
// passing in an errorObj of all the react-hook-form error
// Farther down I am doing a check for a current errror with `errorObj[name]`
const [state, setState] = useState({
error: false,
isFocused: false,
smallLabel: false,
touched: false,
});
const { error, isFocused, smallLabel } = state;
const onInputFocus = () => {
setState({
...state,
smallLabel: true,
isFocused: true,
});
};
const validation = () => {
// if react-hook-form has an error for this form field
if (errorObj[name]) {
setState({
...state,
error: errorMsg,
});
} else {
setState({
...state,
error: false,
});
}
};
const onInputBlur = e => {
// if react-hook-form has an error for this form field
if (errorObj[name]) {
setState({
...state,
error: errorMsg,
isFocused: false,
});
} else if (e.target.value === '') {
setState({
...state,
isFocused: false,
smallLabel: false,
});
} else {
setState({
...state,
error: false,
isFocused: false,
});
}
};
const handleChange = e => {
onChange(e.target.value);
if (error) {
validation(e);
}
};
const renderLabel = () => {
const className = `${placeHolderText || smallLabel ? 'small ' : ''} ${isFocused ? 'focus ' : ''} ${error ? ' error' : ''}`;
if (label) {
return (
<Label htmlFor={id} className={className}>
{label}
</Label>
);
}
return null;
};
const renderError = () => {
if (errorObj[name]) {
return <ErrorDiv>{error}</ErrorDiv>;
}
return null;
};
return (
<div className={wrapperCss}>
{renderLabel()}
// This version uses the react-hook-form Controller in stories file for the ref so doesn't use ref as a prop here
<Input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
data-auid={testId}
data-error={!!error}
id={id}
maxLength={maxLength}
onBlur={e => onInputBlur(e)}
onChange={e => handleChange(e)}
onFocus={() => onInputFocus()}
placeholder={placeHolderText}
type={type}
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
name={name}
/>
{renderError()}
</div>
);
};
InputBox.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
maxLength: PropTypes.number,
name: PropTypes.string,
onChange: PropTypes.func.isRequired,
placeHolderText: PropTypes.string,
type: PropTypes.string,
validate: PropTypes.func,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
InputBox.defaultProps = {
id: '',
label: null,
maxLength: 51,
name: '',
placeHolderText: '',
type: 'text',
validate: null,
value: '',
};
export default InputBox;
import React, { useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { storiesOf } from '@storybook/react';
import { withThemesProvider } from 'storybook-addon-emotion-theme';
import styled from 'react-emotion';
import { useScreenSize } from '../../utils/mediaEffects';
import checkoutTheme from '../theme';
import InputBox from './InputBox';
// import { nameFieldValidate, phoneValidate } from '../validations';
import { formatPhoneNumber } from '../../utils/stringUtils';
const FIRST_NAME_ERROR = 'Please enter a valid first name';
const LAST_NAME_ERROR = 'Please enter a valid last name';
const PHONE_ERROR = 'Please enter a valid phone number';
const StyledDiv = styled('div')`
margin: 10px 20px;
width: 250px;
max-width: ${props => props.maxWidth};
`;
storiesOf('Checkout/InputBox', module)
.addDecorator(withThemesProvider([checkoutTheme]))
.add('Default', () => {
const {
control,
register,
// handleSubmit,
watch,
errors,
} = useForm();
// const onSubmit = data => {
// // eslint-disable-next-line no-console
// console.log(data);
// };
// Watch input values via their "name"
// eslint-disable-next-line no-console
console.log(watch('firstName'));
// eslint-disable-next-line no-console
console.dir(errors);
const windowSize = useScreenSize();
const [placeHolder, setPlaceHolder] = useState('');
const [fName, setFName] = useState('');
const [lName, setLName] = useState('');
const [mobNumber, setMobNumber] = useState('');
return (
<div>
<h5 style={{ margin: '10px' }}>InputBox component with different labels and validations</h5>
<StyledDiv maxWidth={windowSize.innerWidth - 25}>
// One way to setup is with a Controller HOC
// https://react-hook-form.com/advanced-usage#ControlledmixedwithUncontrolledComponents
// Validation not working for me yet this way
<Controller
as={<InputBox onChange={setPlaceHolder} value={placeHolder} />}
name="firstName"
errorObj={errors}
control={control}
defaultValue=""
ref={register({ pattern: /^[A-Za-z]+$/i })}
/>
// I also tried the other method of passing a ref and using reactForwarRef, but couldn't get the "register" function working.
// https://react-hook-form.com/get-started#Adaptingexistingform
<InputBox
errorObj={errors}
id="dummy"
label="Using Place Holder Text"
name="placeHolder"
placeHolderText="hello no validations on me"
onChange={setPlaceHolder}
ref={register({ pattern: /^[A-Za-z]+$/i })}
value={placeHolder}
/>
</StyledDiv>
<StyledDiv maxWidth={windowSize.innerWidth - 25}>
<InputBox
errorObj={errors}
errorMsg={FIRST_NAME_ERROR}
id="firstName"
label="First Name"
name="firstName"
onChange={setFName}
value={fName}
ref={register({ pattern: /^[A-Za-z]+$/i })}
// validate={e => nameFieldValidate(e, FIRST_NAME_ERROR)}
/>
</StyledDiv>
<StyledDiv maxWidth={windowSize.innerWidth - 25}>
<InputBox
errorObj={errors}
errorMsg={LAST_NAME_ERROR}
id="lastName"
label="Last Name"
name="lastName"
onChange={setLName}
ref={register({ pattern: /^[A-Za-z]+$/i })}
value={lName}
/>
</StyledDiv>
<StyledDiv maxWidth={windowSize.innerWidth - 25}>
<InputBox
errorObj={errors}
errorMsg={PHONE_ERROR}
id="mobileNumber"
type="tel"
label="Mobile Number"
name="mobileNumber"
onChange={setMobNumber}
ref={register({ pattern: /^[A-Za-z]+$/i })}
value={formatPhoneNumber(mobNumber)}
/>
</StyledDiv>
</div>
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment