I would like to use useEffect to rerender a field if values of formik has changed. But it doesn't work..
Actually I created a little example to show my problem.
Here I have one field 'title', if this field has changed (when we write in it) it should call useEffect and print 'update!' but it doesn't!!
const FormikWidgetConfigurator = (props) => {
useEffect(() => {
// doesn't work if values has changed
console.log('update!')
}, [props.values]);
return (
<Form onSubmit={ props.handleSubmit } noValidate>
<Form.Group className='py-3' >
<Col md='6' style={{ padding: '0px' }}>
<Form.Group controlId='title'>
<Form.Control
type='text'
value={ props.values.title }
onChange={props.handleChange}
/>
</Form.Group>
</Col>
</Form.Group>
</Form>
)
}
const WidgetConfigurator = withFormik({
mapPropsToValues(props) {
return {
title: 'No Title'
};
},
validationSchema: props => Yup.object().shape({title: Yup.string()}),
handleSubmit(values, { setSubmitting }) {// handle submit}
})(FormikWidgetConfigurator);
export default WidgetConfigurator;
EDIT: Actually it works as expected. (i didn't change anything)
Thanks!
Using vanilla Formik your approach works.
My guess is that the issue is in your custom components, Form.Control, or Form.Group
It's not a great solution, but a hack I found is to have an invisible button inside a Formik form; it has access to all of Formik's attributes and will do whatever logic is needed in its onClick. Then from a useEffect you can simulate the click of that button via document.getElementById("..").click().
// Style it to be invisible
<Button id="testButton" type="button" onClick={() => {
setFieldValue('test', '123');
setTouched({});
// etc. any Formik action
}}>
</Button>
useEffect:
useEffect(() => {
document.getElementById("testButton").click(); // Simulate click
}, [dependency);
Related
In the project I'm working, I have wrapped the input around a form, and the idea is when the user types something and clicks enter, the data is logged. But the issue is I'm not getting such logs.
Here is my code :
Index.tsx
const onFinish = (values: any) => {
console.log("yoyo");
console.log(values);
};
<Form name="control-ref" onMouseEnter={onFinish}>
<Form.Item name="note" rules={[{ required: true }]}>
<InputField
variant="search"
placeholder="Search"
addonAfter={selectAfter}
className="search-field"
/>
</Form.Item>
</Form>
The InputField with the specified variant returns this components
const SearchField: React.FC<IInputFieldProps> = (props) => {
const { placeholder } = props;
return (
<Input
className="search-field"
placeholder={placeholder || 'Search'}
{...props}
/>
);
};
Please help
Log the data when the form is submitted. Forms can be submitted by pressing enter.
<Form name="control-ref" onFinish={(values: any) => {
console.log("yoyo");
console.log(values);
}}>
{...}
</Form>
Edit: updated submission callback according to ant design react docs
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.
I'm trying to console log the value of the TextField input in material ui while using typescript in react. But I got undefined in the console. I tried several solutions, but none worked.
Here's my code:
const HomePage: React.FC = () => {
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log((e.currentTarget as HTMLInputElement).value);
};
return (
<Container maxWidth="xl">
<div style={{width: '385px', margin: 'auto'}}>
<form onSubmit={(e) => {onSubmit(e)}}>
<Stack direction='row' spacing={1}>
<CustomTextField placeholder="Enter name" label="Name" type="text" variant="outlined" name="name" />
<Button type="submit" variant="contained" size='large'>Add</Button>
</Stack>
</form>
</div>
</Container>
)
}
export default HomePage;
I think you may have conflated two examples on dealing with input values to get an incorrect solution.
Because the onSubmit handler is attached to the form, it will have access to all the input values within that form within event.target.elements[..].value, of which there could be many. You can then select the value you want using the name attribute of the input, in this case you have set it to name="name" so your onSubmit handler can access it as such:
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log(e.target.elements.name.value);
};
Edit: you could also access the input directly by dropping the .elements, so e.target.name.value would also work.
What I think you may have confused this with is an onChange handler attached to an input, such as <CustomTextField />, where because there is only one input the event object is able to hold the value at event.target.value.
I managed to get the value by using the following:
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const name = (e.currentTarget.elements.namedItem('name') as HTMLInputElement).value;
};
I have created a very simple modal from antd, and i want to use this basic modal as a generic modal, for many parts of my app. The thing i cant figure out though is how to first use a react hook for a function with parameters and then how i can pass that function into the modal component as props.
const [visible, setVisible] = useState(false)
const [title, setTitle] = useState('New Folder')
const [inputValue, setInputValue] = useState('Untitled Folder')
const [buttonText, setButtonText] = useState('Create')
const [submissionAction, setSubmissionAction] = useState('New Folder')
<Modal
width={300}
centered={true}
visible={visible}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
Cancel
</Button>,
<Button
key="submit"
type="primary"
loading={loading}
onClick={() => {
form
.validateFields()
.then((values) => {
console.log(values)
if (submissionAction === 'New Folder') {
createNewFolder(values.name)
} else if (submissionAction === 'New File') {
createNewFile(values.name)
}
setVisible(false)
})
.catch((info) => {
console.log('Validate Failed:', info)
})
}}
>
{buttonText}
</Button>,
]}
>
<div
style={{
fontSize: '16px',
fontWeight: 'bold',
paddingBottom: '15px',
color: 'black',
}}
>
{title}
</div>
<Form
form={form}
layout="vertical"
name="form_in_modal"
initialValues={{ name: inputValue }}
>
<Form.Item
rules={[
{
required: true,
message: 'Please enter a name',
},
]}
name="name"
>
<Input type="textarea" value={inputValue} />
</Form.Item>
</Form>
</Modal>
Notice how variables like buttonText or title are dynamic, i am using hooks for those, and later when i make this modal into a generic component i will pass in the values as props. How can i pass in the value of the functions in onClick as props though? And how would i even store them using state to begin with?
Like instead of using a string (submissionAction) to represent which function to call, say I had a hook for the function, so when a user clicks a button to create a new folder, i will set the value of that hook to be the corresponding function createNewFolder, otherwise createNewFile or editFolder etc. Then I will pass the value of this hook into a generic modal component, so that i can reuse it all across my app. How can i do this? Or is there a better way to do what i'm trying to achieve. Do i need to create a modal component in each component where im using a modal or can i have one modal used across all?
I'm trying to use react-hook-form together with the antd <Input /> component
I'm not getting reset to work with <Controller />
Here is my code:
const NormalLoginForm = () =>{
const {reset, handleSubmit, control} = useForm();
const onSubmit = handleSubmit(async ({username, password}) => {
console.log(username, password);
reset();
});
return (
<form onSubmit={onSubmit} className="login-form">
<Form.Item>
<Controller as={<Input
prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
autoFocus={true}
placeholder="Benutzername"
/>} name={'username'} control={control}/>
</Form.Item>
<Form.Item>
<Controller as={<Input
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>}
type="password"
placeholder="Passwort"
/>} name={'password'} control={control}/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
</Form.Item>
</form>
);
}
I'm expecting that the two input fields are getting cleared when the form is submitted. But that doesn't work.
Am I missing something here?
Example on Stackblitz
https://stackblitz.com/edit/react-y94jpf?file=index.js
Edit:
The RHFInput mentioned here React Hook Form with AntD Styling is now part of react-hook-form and has been renamed to Controller. I'm already using it.
I've figured out that chaning
reset();
to
reset({
username:'',
password:''
});
solves the problem.
However - I wanted to reset the whole form without explicitly assigning new values.
Edit 2:
Bill has pointed out in the comments that it's almost impossible to detect the default values for external controlled inputs. Therefore we're forced to pass the default values to the reset method. That makes totally sense to me.
You must wrapper the components for antd and create a render component, it is very similar if you use Material UI, So the code can be like:
import { Input, Button } from 'antd';
import React from 'react';
import 'antd/dist/antd.css';
import {useForm, Controller} from 'react-hook-form';
const RenderInput = ({
field: {
onChange,
value
},
prefix,
autoFocus,
placeholder
}) => {
return (
<Input
prefix={prefix}
autoFocus={autoFocus}
placeholder={placeholder}
onChange={onChange}
value={value}
/>
);
}
export const NormalLoginForm = () =>{
const {reset, handleSubmit, control} = useForm();
const onSubmit = ({username, password}) => {
console.log(username, password);
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="login-form">
<Controller
control={control}
name={'username'}
defaultValue=""
render={ ({field}) => (
<RenderInput
field={field}
autoFocus={true}
placeholder="Benutzername"
/>
)}
/>
<Controller
render={ ({field}) => (
<RenderInput
field={field}
type="password"
placeholder="Passwort"
/>
)}
defaultValue=""
name={'password'}
control={control}
/>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
>
Log in
</Button>
</form>
);
}
export default NormalLoginForm;
You can notice that I did't put the Icon ,it was because I tested using the version 4 for antd and something change in how to use the icons.