Custom input from react-form-hook not working - javascript

Following the example here I have a custom input component:
Input.tsx
import React from "react";
export default function Input({label, name, onChange, onBlur, ref}:any) {
return (
<>
<label htmlFor={name}>{label}</label>
<input
name={name}
placeholder="Jane"
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</>
);
}
An example of the usage:
import {useAuth} from '../components/AuthContextProvider'
import { useForm, SubmitHandler } from "react-hook-form";
import Input from '../components/Input'
function Subscriptions({ Component, pageProps }: any) {
const { user } = useAuth()
const { register, handleSubmit, formState: { errors } } = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = async data => {
console.log(data, 'data sdfdsg')
}
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<Input label="name" {...register('name')}/>
<button type='submit'>Add kid</button>
</form>
</>
)
}
export default Subscriptions
Here's my package version:
"react-hook-form": "^7.34.2"
Any ideas what I'm doing wrong here?
The custom input receives undefined but it works with normal <input /> tags.

I've used the example in a codesandbox and it threw errors about ref and it suggested using React.forwardRef, change your custom Input to this:
function Input({ label, name, onChange, onBlur }, ref) {
return (
<>
<label htmlFor={name}>{label}</label>
<input
name={name}
placeholder="Jane"
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</>
);
}
const MyInput = React.forwardRef(Input);
export default MyInput;
and by the way, ref is not part of props I don't know why the example has it like that, it's necessary to use forwardRef so it is passed as second argument.
you can see the full example in this codesandbox

Related

Invalid Hook Call with react-hook-form

I am new to react hook form so this may be a simple issue. I just discovered that Controllers do not have the ability to use value as number. This was aggravating , but I eventually found this github issue #8068 which describes the solution as setting an onChange function like this:
<Controller
- rules={{ valueAsNumber: true }}
render={({ field }) => (
<input
- onChange={field.onChange}
+ onChange={(event) => field.onChange(+event.target.value)}
type="number"
/>
)}
/>
So I did some slight altering and came up with this code
import React, { ChangeEvent } from 'react'
import { Controller } from 'react-hook-form'
import { getPlaceholder } from './getPlaceholder'
import { IInput } from './types'
const NumberInput: React.FC<IInput> = ({ control, name, ...props }) => {
const placeholder = getPlaceholder({ type: "number" });
const numericalOnChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value === '') return null;
return +event.target.value;
}
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, ...field } }) => (
<input
{...props}
{...field}
type="number"
placeholder={placeholder}
onChange={(event) => {
const value = numericalOnChange(event)
onChange(value)
}}
className="h-[20px] pl-[4px] py-[8px] bg-transparent border-b
border-b-[#646464] focus:border-b-[#3898EC] text-[13px]
text-[#F00] placeholder-[#646464] outline-none m-1 w-full"
/>
)}
/>
)
}
export default NumberInput
which in theory should work, but ends up providing an Invalid Hook Call Error.

Delete tag in uncontrolled "ChipInput" using react hook form

I'm using react-hook-form to handle form values, Its working fine for all other input types like TextFeild, Select from material but facing issues with "material-ui-chip-input" as adding tag working fine but not able to delete tag on click of cross button or hitting backspace. I'm struggling on this from a long. Anyone please help in it.
import React from "react";
import FormControl from "#material-ui/core/FormControl";
import { Controller } from "react-hook-form";
import ChipInput from "material-ui-chip-input";
const ReactHookFormChips = ({
name,
label,
control,
defaultValue,
children,
rules,
error,
chipsError,
...props
}) => {
const labelId = `${name}-label`;
return (
<FormControl {...props}>
<Controller
as={
<ChipInput
label={label}
helperText={chipsError}
error={error}
/>
}
name={name}
control={control}
defaultValue={defaultValue}
rules={rules}
/>
</FormControl>
);
};
export default ReactHookFormChips;
calling this component like
<ReactHookFormChips
id="levelLabel"
name="tags"
label="Select Tags"
control={control}
defaultValue={[]}
margin="normal"
error={!!errors?.tags}
rules={{ required: true }}
chipsError={errors?.tags && "Tag is required."}
/>
I fixed it using render prop.
import React from "react";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import { Controller } from "react-hook-form";
import ChipInput from "material-ui-chip-input";
const ReactHookFormChips = ({
name,
label,
control,
defaultValue,
children,
rules,
error,
chipsError,
...props
}) => {
const labelId = `${name}-label`;
return (
<FormControl {...props}>
<Controller
render={({ onChange, onBlur, value }) =>
<ChipInput
onChange={onChange}
label={label}
helperText={chipsError}
error={error}
/>
}
name={name}
control={control}
defaultValue={defaultValue}
rules={rules}
/>
</FormControl>
);
};
export default ReactHookFormChips;

Running into a state error when attempting to change an input onChange

I when I pass state down from a parent component the setState keeps throwing an error stating that setEmail is not a function on event change. I was wondering if there is a simple fix to this.
When I try typing in a string within the email input it throws the following error:
Error: Unhandled Runtime Error
TypeError: setEmail is not a function
Parent Component: (Next.js Application)
import React, { useState, useContext, useEffect } from "react";
import "../styles/globals.css";
import "bootstrap/dist/css/bootstrap.min.css";
function MyApp({ Component, pageProps }) {
const [tester, setTester] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return (
<Component
{...pageProps}
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
/>
);
}
export default MyApp;
Child Component:
export default function Signup(props) {
const router = useRouter();
const { email, setEmail, password, setPassword, setHasAccount } = props;
// function to handle submitting a new user on click
const onSubmit = (event) => {
event.preventDefault();
router.push("/questions");
};
return (
<div>
<div>
<Container>
<form onSubmit={onSubmit} className={styles.formSize}>
<FormGroup>
<Input
className={styles.inputContainer}
value={email}
placeholder="john.doe#example.com"
onChange={(e) => setEmail(e.target.value)}
/>
<Input
className={styles.inputContainer}
placeholder="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
<Button color="primary" type="submit">
Signup
</Button>
<p
className={styles.loginPhrase}
onClick={(event) => {
event.preventDefault();
setHasAccount(true);
}}
>
Have an account? Login Here
</p>
</FormGroup>
</form>
</Container>
</div>
<link rel="icon" href="/favicon.ico" />
</div>
);
}
I took your code and ported it over to a sandbox and it works just fine, I believe some way or another you are passing over props incorrectly. Here is a copy of the sandbox so you can see it working in action
https://codesandbox.io/s/gallant-microservice-yig2q?file=/src/App.js
You need to check that the parent of this component is passing in a prop called setEmail and that it is a function. It looks like maybe you've forgotten to pass it in as a prop.

How to handle onSubmit inside custom component

My custom component has onChange event, which is working pretty good, but when I tried onSubmit, It does not work.
Alert does not display.
Currently my data provider get all values from inputs except my custom component, what should I do?
what's wrong with the code?
It's possible to pass data from this custom component to the parrent form?
Parrent form:
export const smthEdit = props => (
<Edit {...props} title={<smthTitle/>} aside={<Aside />}>
<SimpleForm>
<DisabledInput source="Id" />
<TextInput source="Name" />
<ReferrenceSelectBox label="Socket" source="SocketTypeId" reference="CPUSocketType"></ReferrenceSelectBox>
<DateField source="CreatedDate" showTime
locales={process.env.REACT_APP_LOCALE}
disabled={true} />
</SimpleForm>
</Edit>
);
My custom component (ReferrenceSelectBox):
handleSubmit(event) {
alert('smth');
}
render() {
return (
<div style={divStyle}>
<FormControl onSubmit={this.handleSubmit}>
<InputLabel htmlFor={this.props.label}>{this.props.label}</InputLabel>
<Select
multiple
style={inputStyle}
value={this.state.selectedValue}
onChange={this.handleChange}
>
{this.renderSelectOptions()}
</Select>
</FormControl>
</div>
);
}
Error is change FormControl to form
<form onSubmit={(event) => this.handleSubmit(event)}>
<InputLabel htmlFor={this.props.label}>{this.props.label}</InputLabel>
<Select
multiple
style={inputStyle}
value={this.state.selectedValue}
onChange={this.handleChange}
>
{this.renderSelectOptions()}
</Select>
</form>
Form Input.js
import React, { Component } from 'react';
import { Edit, SimpleForm, TextInput } from 'react-admin';
import SaveUpdate from './button/saveupdate.js';
export default class MemberDetail extends Component {
render(){
return (
<Edit title={"Member Detail"} {...this.props} >
<SimpleForm redirect={false} toolbar={<SaveUpdate changepage={changepage}>
<TextInput source="name" label="name"/>
</SimpleForm>
</Edit>)
}
}
/button/saveupdate.js
import React, { Component } from 'react';
import { Toolbar, UPDATE, showNotification, withDataProvider, GET_ONE} from 'react-admin';
import Button from '#material-ui/core/Button';
class SaveUpdate extends Component {
doSaveUpdate = (data) => {
const { dataProvider, dispatch } = this.props
dataProvider(UPDATE, endPoint, { id: data.id, data: { ...data, is_approved: true } })
.then((res) => {
dispatch(showNotification('Succes'));
})
.catch((e) => {
console.log(e)
dispatch(showNotification('Fail', 'warning'))
})
}
render(){
return (
<Toolbar {...this.props}>
<Button variant='contained' onClick={handleSubmit(data => { this.doSaveUpdate(data) })}>
SAVE
</Button>
</Toolbar>
)
}
export default withDataProvider(SaveUpdate);
This handlesubmit extra withDataProvider

Redux Form won't allow Initialize Form state values to be edited

I have a redux-form component that I correctly passing in the initial form state values but when I click inside the form I cannot edit the values. I am have followed all the documentation like the following link:
Initialize From State
And I also followed the documentation to implement a custom input:
Will redux-form work with a my custom input component?
So I am trying to figure out what am I missing? Here is my form component:
EditJournalForm.jsx
import "./styles/list-item.scss";
import {Button, ButtonToolbar} from "react-bootstrap";
import {connect} from "react-redux";
import {Field, reduxForm} from "redux-form";
import InputField from "../form-fields/InputField";
import PropTypes from "prop-types";
import React from "react";
class EditJournalForm extends React.Component {
render() {
//console.log('EditJournalForm this.props', this.props);
const {closeOverlay, handleSubmit, initialValues, pristine, submitting,} = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<div className="form-field">
<Field
component={props =>
<InputField
content={{val: initialValues.title}}
updateFn={param => props.onChange(param.val)}
{...props}
/>
}
label="Journal title"
name="title"
type="text"
/>
</div>
<div className="form-field">
<Field
component={props =>
<InputField
content={{val: initialValues.description}}
updateFn={param => props.onChange(param.val)}
{...props}
/>
}
componentClass="textarea"
label="Description"
name="description"
rows="5"
type="text"
/>
</div>
<div className="form-button-group">
<ButtonToolbar>
<Button
bsSize="small"
style={{"width": "48%"}}
onClick={() => {
if (closeOverlay) {
closeOverlay();
}
}}
>
Cancel
</Button>
<Button
bsSize="small"
disabled={pristine || submitting}
style={
{
"backgroundColor": "#999",
"width": "48%"
}}
type="submit"
>
Add
</Button>
</ButtonToolbar>
</div>
</div>
</form>
);
}
}
EditJournalForm.propTypes = {
"closeOverlay": PropTypes.func,
"handleSubmit": PropTypes.func.isRequired,
"pristine": PropTypes.bool.isRequired,
"submitting": PropTypes.bool.isRequired,
"initialValues": PropTypes.object
};
EditJournalForm.defaultProps = {
"closeOverlay": undefined
};
export default reduxForm({
form: "editJournal",
enableReinitialize: true
})(connect((state, ownProps) => {
return {
initialValues: {
"title": state.bees.entities.journals[ownProps.journal.id].attributes.title,
"description": state.bees.entities.journals[ownProps.journal.id].attributes.description,
}
};
}, undefined)(EditJournalForm));
and here is my custom input:
InputField.jsx
import {ControlLabel, FormControl, FormGroup} from "react-bootstrap";
import PropTypes from "prop-types";
import React from "react";
const InputField = ({input, label, content, updateFn, type, ...props}) => (
<FormGroup >
<ControlLabel>
{label}
</ControlLabel>
<FormControl
{...props}
{...input}
value={content}
onChange={updateFn}
type={type}
/>
</FormGroup>
);
export default InputField;
InputField.propTypes = {
"input": PropTypes.object.isRequired,
"label": PropTypes.string.isRequired,
"type": PropTypes.string.isRequired,
"content": PropTypes.object,
"updateFn": PropTypes.func
};
Try to call onChange function of input field:
const InputField = ({input, label, content, updateFn, type, ...props}) => (
<FormGroup>
<ControlLabel>
{label}
</ControlLabel>
<FormControl
{...props}
{...input}
value={content}
onChange={(e) => {
input.onChange(e);
updateFn(e);
}}
type={type}
/>
</FormGroup>
);
I see at least one problem - you are assigning the content prop as an object with a val property, but in your custom InputField, you are setting value={content}, so that is actually the object with { val: 'the value' } as opposed to the actual value ('the value' in this example).
With redux-form, it isn't necessary to manually assign from initialValues. By having a name property on the Field, it will be correctly assigned for you.

Categories