Automatically Passing Props on Component - reactjs

I have created an Input component that I have styled with Styled Components. I am now using that Input component with Formik. I would like to be able to automatically set the onChange, onBlur and value props rather than have to set them each and every time (like what happens if I would use the Formik Field component).
That is, right now this is how my component looks when used:
<Input
name="firstName"
onBlur={handleBlur}
onChange={handleChange}
value={values.firstName}
/>
What I would like the component to look like when being used is this:
<Input name="firstName" />
Then, behind the scenes, onBlur would be set to handleBlur, onChange would be set to handleChange and value would be set to values.[name]. That is, to the value of the name prop. So, in this example, it would be set to values.firstName. If the name prop was set to lastName, then the value prop would automatically be set to values.lastName.
Any idea how I can do this?
NOTE: I know that the Field prop from Formik does this, but I want to use my custom Input component instead.
UPDATE
Here is some other code that may be relevant to answering this question.
Input Component
export const Input = props => {
const {
forId,
name,
placeholder,
} = props
const titleCase = startCase(name)
return (
<InputBase {...props}>
<InputSection>
<InputContent
id={forId ? forId : name}
placeholder={placeholder ? placeholder : titleCase}
type="text"
{...props}
/>
</InputSection>
</InputBase>
)
}
InputContent Component
export const InputContent = styled.input`
// STYLES
`
Formik with Form
<Formik
render={props => {
const {
handleBlur,
handleChange,
values,
} = props
return (
<Form>
<Input
name="firstName"
onBlur={handleBlur}
onChange={handleChange}
value={values.firstName}
/>
<Button type="submit">Submit</Button>
</Form>
)
}}
initialValues={{firstName: ''}
validationSchema={validationSchema}
/>

I don't think this is good idea. But what you can do is create HOC and wrap it
// lasyWrapper.js
export function lazyWrapper(Input) {
return LazyWrapper extends React.Component {
render() {
return (
<Input
{...this.props}
name={this.props.name}
onBlur={this.props.handleBlur}
onChange={this.props.handleChange}
value={this.props.values[this.props.name]}
/>
)
}
}
}
// Input.js
export default lazyWrapper(Input)
// use somewhere
<Input
name="firstName"
{...this}
/>
But this is really BAD idea.

Related

Validation for Input form component React

I have a form component that include multiple TextFields and each one have different validation and error. Assume my TextField had custom validation prop that will take some conditions and show validation either onChange or onBlur for each TextField. I also have custom error prop that display message without condition.
const Address = ({
onChange,
required,
error,
value = {},
validation = undefined,
}: AddressProps) => {
const validator = {
validationAddress: (value: string) => (value.length === 0) ? 'Address is required' : '',
validationCity: (value: string) => (value.length === 0) ? 'City is required' : '',
}
const handleChange = (event) => {
...
}
return (
<React.Fragment>
<TextField
id="address"
label="address"
type="text"
required={required}
onChange={handleChange}
value={value.address}
validationEvent="change"
validation={validator.validationAddress}
/>
<TextField
id="city"
label="city"
type="text"
required={required}
onChange={handleChange}
value={value.city}
validationEvent="change"
validation={validator.validationCity}
/>
</React.Fragment>
export default Address;
After that I will implement my Address component in an App with submit button.
const App = () => {
const handleSubmit = () => {
...
}
return (
<Address />
<button type='submit' onClick={handleSubmit}>
Submit
</button>
)
}
export default App;
I saw many examples that input and form put together with submit button but I am struggling with handle them separately this way. My task is need to handle validation in Address component but have submit button as an optional option outside the Address component. My goal is validation message should not show until Submit button is clicked.
Try this:
move validator to parent component
pass validatorMessage useState var to Address component
call the validator by the click of submit button
Now the message is managed in the parent component and you control when the submit button displays it.
I would suggest to consider a different approach: there is no need to manually implement validation, we can use the browser built-in validation feature:
https://medium.com/p/491327f985d0
Essentially I use standard HTML mark-up and properties.
Implementation example:
export default function App() {
const onSubmit = (data: FormData) => {
console.log(Object.fromEntries(data.entries()));
};
return (
<div className="App">
<Form onSubmit={onSubmit}>
<h2>A simple form</h2>
<TextInput label="Name:" id="name" name="name" required />
<TextInput
label="Email:"
id="email"
type="email"
name="email"
required
/>
<TextInput label="Address:" id="address" name="address" />
<TextInput
label="Tel:"
id="tel"
name="tel"
type="tel"
pattern="((\+44(\s\(0\)\s|\s0\s|\s)?)|0)7\d{3}(\s)?\d{6}" // don't rely on this
/>
<button>Submit</button>
</Form>
</div>
);
}
The Form component itself is very simple:
<form
action={action}
onSubmit={handleSubmit}
noValidate
className={style.form}
>
<div className={style.wrapper}>{children}</div>
</form>
It sets the noValidate attribute to prevent the default error notification to pop up.
The input boxes are wrapped in the form element.
The form element has an onSubmit event handler: it gets triggered when user clicks on the submit button OR when the user hit return using the keyboard.
At that point we use e.preventDefault(); to prevent default form submission and handle it manually.

react-hook-form access to validation rules in nested component

I'd like to access to the validation rules defined in react-hook-form's Resister in the nested component to dynamically display required indicator(*).
Is there a way to access from Nested component?
I don't want to repeat by passing as a prop.
<TextBox ref={register({ required: true })} label="Serial No" name="serialNo" />
const TextBox = React.forwardRef(({ label, name }, ref) => (
<>
<label htmlFor={name}>
{label} {***required*** && <span>*</span>}
</label>
<input type="text" name={name} id={name} ref={ref} />
</>
))
have a look at https://react-hook-form.com/api#useFormContext
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input name="test" ref={register} />;
}
which allow you to access all hook form methods in the nested component level.

How to get formvalues in handlechange?

In my form I have an onChange event on one of the formfields:
<div>
<label>Last Name</label>
<div>
<Field
name="lastName"
component="input"
type="text"
placeholder="Last Name"
onChange={() => this.handleChange()}
/>
</div>
</div>
When handleChange gets fired I want to get the formvalues :
handleChange(){
//do calculation
console.log('handlechange',this.props.values)
}
At the moment I am getting this.props.values = undefined? How can I get the formvalues?
You'll need to create a custom input.
You'll need to intercept and update redux's input.onChange function.
Optional -- If you want other form values to influence this value, then utilize reduxForm's formSelector with react-redux's connect's mapStateToProps to access formProps within the component in this.props (the working example below already includes this functionality)
components/CustomInput.js
import React from "react";
const CustomInput = ({
// we'll intercept redux's "onChange" func, while leaving the other
// input props as is in "inputProps"
input: { onChange, ...inputProps },
// the "handleChange" func below is the parent func that will handle input changes
handleChange,
// "rest" contains any additional properties (className, placeholder, type ...etc)
...rest
}) => (
// we spread out the "inputProps" and the "rest" of the props, then we add
// an "onChange" event handler that returns the "event" and the
// input's "onChange" func to our "handleChange" parent func
<input {...inputProps} {...rest} onChange={e => handleChange(e, onChange)} />
);
export default CustomInput;
containers/Form.js
class ControlledFormValue extends PureComponent {
// this parent func will handle updates through the "event.target.value";
// the value can be changed/altered and then passed to the input's
// "onChange" func to update the field
handleChange = ({ target: { value } }, onChange) => {
// this will alter the value by adding a "-" after each input update
onChange(`${value}-`);
setTimeout(() => console.log(this.props.values), 500);
};
render() {
return (
<form onSubmit={this.props.handleSubmit}>
<div>
<label>First Name</label>
<div>
<Field
className="uk-input"
name="firstName"
component={CustomInput}
type="text"
placeholder="First Name"
handleChange={this.handleChange}
/>
</div>
</div>
...etc
</form>
);
}
}
Working example: https://codesandbox.io/s/lx1r4yjwy7

How to use react-select with redux-form?

I am trying to integrate react-select using redux form...
Here is my code
import Select from 'react-select'
import StyledSelectField, { StyledMessages } from './style'
const SelectField = props => {
const { input, label, placeholder, options, meta, ...StyledProps } = props
const { onChange, onBlur, onFocus } = props.input
return (
<StyledSelectField {...StyledProps} meta={meta} hasValue={!!input.value}>
<label htmlFor={input.name}>{label}</label>
{placeholder && <div className="placeholder">{placeholder}</div>}
<Select
name={input.name}
value={input.value.value}
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
options={options}
{...props}
/>
<StyledMessages>
{meta.touched &&
((meta.error && <span className="error">{meta.error}</span>) ||
(meta.warning && <span className="warning">{meta.warning}</span>))}
</StyledMessages>
</StyledSelectField>
)
}
class TestingCycleForm extends PureComponent {
render() {
const { preMonitoring, handleChange, handleSubmit } = this.props
return (<div>
<Field
label="18%"
name="patientPercentage"
className="form-control"
component={SelectField}
options={Config.get('/PATIENT_PERCENTAGE')}
/>
</div>)
}
}
All things are working but my input field gets cleared on focus out what I am doing wrong here?
Thanks in advance... Any help would be appreciated
You say “focus out” - does that mean it clears on blur? If so, does setting onBlurResetsInput and onCloseResetsInput to false help?
Update: here's the link to the props table from the github readme. You've got to set both onBlurResetsInput and onCloseResetsInput to false at the same time, onBlurResetsInput set to false, by itself, will do nothing.
And also you need to remove the onBlur prop from the select which causes field clear on Blur
<Select
name={input.name}
value={input.value.value}
onChange={onChange}
onBlurResetsInput={false}
onCloseResetsInput={false}
onFocus={onFocus}
options={options}
{...props}
/>

My onChange function is not being passed down to child

I'm building a higher component where field input is grouped together with some other components. I'm trying to pass down my onChange function to the child component so it can change the state with the new input. all the other props are passed fine and I can see the using chrome dev tools, but even though I can see the onChange function if I console.log the props on the child, it is not present on the form control component which ultimately creates the input text field.
Here is the code, you can notice in the form render the FormGroup which is not using the higher InputFieldGroup component:
higher_component.js
import React from 'react'
import * as FormGroup from 'react-bootstrap/lib/FormGroup'
import * as ControlLabel from 'react-bootstrap/lib/ControlLabel'
import * as FormControl from 'react-bootstrap/lib/FormControl'
export const InputFieldGroup = (props) =>
<FormGroup
controlId={props.controlId}
validationState={props.validationState()}>
<ControlLabel>{props.controlLabel}</ControlLabel>
{console.log(props)} {/* I CAN SEE THE ONCHANGE FUNCTION HERE */}
<FormControl
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder}
onChange={props.handleChange}
/>
<FormControl.Feedback />
</FormGroup>
form.js
export default class RequestQuote extends Component {
...Some other class methods and stuff
handleTextChange = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
render() {
return(
<form>
<InputFieldGroup
controlId='email'
validationState={this.emailValidationState}
controlLabel='email'
type='text'
name='email'
value={this.state.email}
placeholder='Enter Business Email'
onChange={this.handleTextChange}
/>
{/* When I don't use the higher component the onchange works fine */}
<FormGroup>
<ControlLabel>Name</ControlLabel>
<FormControl
type='text'
name='name'
value={this.state.name}
placeholder='Enter Name'
onChange={this.handleTextChange}
/>
<FormControl.Feedback />
</FormGroup>
</form>
)
}
}
Why isn't the onChange function being passed to the child input?
Because it is undefined! In fact you give
onChange={this.handleTextChange}
to the components as a props, if you want to call it do
props.onChange
and not
props.handleChange
otherwise, change the variable onChange={js} to handleChange={js}

Resources