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
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.
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;
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.
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
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.