Forkmik handleChange not updating dropdown value in React.js - javascript

I'm trying to use Form.Select from semantic ui react to build a dropdrown. I'm sending an array of objects with the following properties to fill dropdown options:
DelivererForDropdown = {
key: deliverer.id,
value: deliverer.id,
text: deliverer.userName,
id: deliverer.id
};
The problem I'm facing is that the handleChange event from Formik is not updating my selection. I'm using this event for other inputs and it works well.
<Form.Select
type="text"
placeholder="Repartidor"
name="deliverer"
options={deliverersForDropdown}
search
onChange={handleChange}
error={errors.deliverer}
value={values.deliverer}
/>

You can use Formik function setFieldValue() to manually set the value in the handler onChange():
<Formik
enableReinitialize={true}
initialValues={initialValues}
onSubmit={async (values, { resetForm }) => {
console.log(JSON.stringify(values, null, 2));
}}
>
{({ isSubmitting, values, setFieldValue, setTouched, errors }) => {
return (
<FormikForm>
<Form.Select
type="text"
placeholder="Repartidor"
name="deliverer"
options={deliverersForDropdown}
search={true}
onChange={(event, { value }) => {
setFieldValue("deliverer", value);
setTouched("deliverer", true);
}}
error={errors.deliverer}
value={values.deliverer}
/>
<button type="submit" disabled={isSubmitting}>
SUBMIT
</button>
</FormikForm>
);
}}
</Formik>
Live Demo
Alternatively you can use a 3rd party that automatically binds, for example formik-semantic-ui

Related

React function component parent state change does not re-render child component

I have a parent react function component (Products) which shows a list of products and with a state productInfo which is passed as a prop to the child component (AddEditProductModal)
const Products = () => {
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
const [productInfo, setProductInfo] = useState({});
const showEditModal = async (currentProductInfo) => {
console.log('edit called for key ',currentProductInfo.key)
setIsEditModalVisible(true);
setProductInfo(prevProductInfo => {
return {...prevProductInfo, ...currentProductInfo};
});
};
useEffect(() => {
setIsEditModalVisible(false);
setProductInfo({})
}, []);
return (
<>
<AddEditProductModal
title="Edit Product"
visible={isEditModalVisible}
productInfo={productInfo}
onOk={handleOk}
onCancel={handleCancel}
onFinish={onFinish}
/>
//Table components with columns/actions per row go here
</>
);
};
export default Products;
The child component AddEditProductModal is an antd Modal/Popup which fills the form with prefilled values chosen for current product row as shown below.
const AddEditProductModal = ({ title, visible, productInfo, onOk, onCancel, onFinish }) => {
return (
<Modal
title={title}
visible={visible}
onOk={onOk}
onCancel={onCancel}
>
<Form
name="basic"
labelCol={{
span: 8,
}}
wrapperCol={{
span: 16,
}}
onFinish={onFinish}
initialValues = {productInfo}
>
<Form.Item
label="Key"
name="key"
>
<Input disabled={true} />
</Form.Item>
<Form.Item
label="Image"
name="image"
rules={[
{
required: true,
message: "Please input image!",
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Name"
name="name"
rules={[
{
required: true,
message: "Please input name!",
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Price"
name="price"
rules={[
{
required: true,
message: "Please input price!",
},
]}
>
<Input />
</Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form>
</Modal>
);
};
export default AddEditProductModal;
The productInfo is an object containing props as shown below:
{"key":19,"name":"Cooker","image":"https://.....d16bfa6d9c2e010cadc3fe6885448cbd.jpg_720x720q80.jpg","price":123}
When I click on any row's Edit button, the AddEditProductModal shows correct default values for the product. But when I click on another product/row, the AddEditProductModal still shows old values even though the productInfo state (seen in the profiler) has changed. Basically, the productInfo state has changed in the parent but the child has not re-rendered is what I am thinking.
Can anyone help why the modal shows the info on the first click but second time, fails to re-render and shows old product info ?
Yes, you are right! First, antd Form's API initialValues only works when it is initialized or reset. Second, antd Modal won't destroy after the close. So there is the result you said.
method A: do what you said,
useEffect(() => {
form.setFieldsValue(productInfo);
}, [productInfo]);
method B: destroyOnClose property of Modal set true will also solve your issue, but this is not a good choice!
const AddEditProductModal = ({ title, visible, productInfo, onOk, onCancel, onFinish }) => {
return (
<Modal
title={title}
visible={visible}
onOk={onOk}
onCancel={onCancel}
+destroyOnClose+
>
...
I got the answer to my problem. Rather than react, this seemed like an issue with the antd Form component initialValues. Seems like the initialValues does not change and we need to explicitly update the values. Adding the following to my AddEditProductModal solved my issue:
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(productInfo);
}, [productInfo]);
Hope this helps some antd user some day.

Use select in Semantic UI with Formik

I'm usign Formik to validate some data fields with Semantic UI in React. It works fine with input fields but doesn't work with selectors.
How it works with input fields:
import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
...
const initialValues = {
name: ''
};
const requiredErrorMessage = 'This field is required';
const validationSchema = Yup.object({ name: Yup.string().required(requiredErrorMessage) });
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field name="name" as={Input} placeholder="name" /> // here is the input
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
</Formik>;
However, when the as={Input} is replaced by as={Select}, it doesn't work. The dropdown gets opened when I click on the selector, it shows the options (company1 and company2), I click on one of them but it does not work -> the value shown is still the placeholder value.
import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field
name="name"
as={Select} // here is changed to Select
options={companyOptions} // the options
placeholder="name"
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
</Formik>;
The reason is because Formik passes an onChange function into the component. However, the Select (which is just syntactic sugar for dropdown https://react.semantic-ui.com/addons/select/) onChange prop uses a function of shape handleChange = (e: React.SyntheticEvent<HTMLElement>, data: SelectProps) => this.setState({value: data.value}) (https://react.semantic-ui.com/modules/dropdown/#usage-controlled) which is different than the conventional handleChange = (event: React.SyntheticEvent<HTMLElement>) => this.setState({value: event.target.value}) which formik uses.
So an easy solution would be to use the Formik function setFieldValue.
In your code, the implementation would be something like this:
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched, setFieldValue, values }) => {
const handleChange = (e, { name, value }) => setFieldValue(name, value));
return (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field
name="name"
as={Select} // here is changed to Select
options={companyOptions} // the options
placeholder="name"
onChange={handleChange}
value={values.name}
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
}
</Formik>;
Here's a somewhat working version using your code. The fields update (verified with a console.log), but the submit button isn't working 🤷‍♂️: https://codesandbox.io/s/formik-and-semanticui-select-gb7o7
This is a somewhat incomplete solution and does not handle the onBlur and other injected formik functions. It also defeats the purpose of Formik somewhat. A better solution would be to create a HOC that wraps semantic ui components to use Fromik correctly: https://github.com/formium/formik/issues/148.
And lastly, you can skip the Semantic UI Select and use the Semantic UI HTML controlled select: https://react.semantic-ui.com/collections/form/#shorthand-field-control-html. Notice the select vs Select. This will work natively with Formik without the need to pass in onChange and value.
I recently created a binding library for Semantic UI React & Formik.
Link: https://github.com/JT501/formik-semantic-ui-react
You just need to import Select from the library and fill in name & options props.
// Import components
import { Select, SubmitButton } from "formik-semantic-ui-react";
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Select
name="name"
selectOnBlur={false}
clearable
placeholder="name"
options={companyOptions}
/>
</Grid.Column>
</Grid>
</CreationContentContainer>
<SubmitButton>
Create company
</SubmitButton>
</div>
</Form>
)}
</Formik>;

Setting the default value inside the input field in the antd library after calling the function in useEffect

I have the loadProfile function that I want to call in useEffect. If the loadProfile function is called within useEffect, the name Mario should be displayed inside the input name field. How can I set the default value in the antd library inside input? I try to use defaultValue but the input field remains empty.
Example here: https://stackblitz.com/edit/react-311hn1
const App = () => {
const [values, setValues] = useState({
role: '',
name: '',
email: '',
password: '',
buttonText: 'Submit'
});
useEffect(() => {
loadProfile();
}, []);
const loadProfile = () => {
setValues({...values, role, name="Mario", email});
}
const {role, name, email, buttonText} = values;
const updateForm = () => (
<Form
{...layout}
name="basic"
initialValues={{
remember: true,
name: name
}}
>
<Form.Item
label= 'Name'
name='name'
rules={[
{
required: true,
message: 'Please input your name!',
},
]}
>
<Input
defaultValue= {name}
/>
</Form.Item>
<Form.Item
label="Email"
name="email"
value={email}
rules={[
{
type: 'email',
required: true,
message: 'Please input your email!',
},
]}
>
<Input
/>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
{buttonText}
</Button>
</Form.Item>
</Form>
);
return (
<>
<Row>
<Col span={24} style={{textAlign: 'center'}}>
<h1>Private</h1>
<p>Update Form</p>
</Col>
</Row>
{updateForm()}
</>
);
};
You have to make three changes to your code. This is a working component, I also extracted your component and put the appropriate state in there. I also made it functional.
https://stackblitz.com/edit/react-tlm1qg
First change
setValues({...values, role, name="Mario", email});
to
setValues({...values, name: "Mario"});
This will properly set the state.
Second change:
Next, you should notice that if you set defaultValue="test123" it still won't work, something is up. Remove name from Form.Item and boom it works. test123 shows up. But if you put values.name in there, it still doesn't work!
Third Change:
That's because defaultValue only sets that value right when the component is created, not on mount. So you have to use value={values.name} and it will set that value once on mount per your useEffect
In the demo component I also added a change handler for you so the user can type in there, if you wanted that.
If you look at the FAQ for Ant Design it says:
Components inside Form.Item with name property will turn into
controlled mode, which makes defaultValue not work anymore. Please try
initialValues of Form to set default value.
Ant Design is taking over control - so you don't have to set value={name} and onChange.
You want to set the values AFTER the component is created. So to do that you need
const [form] = Form.useForm();
React.useEffect(() => {
form.setFieldsValue({
username: 'Mario',
});
}, []);
and in you Form make sure you add:
<Form
{...layout}
name="basic"
initialValues={{
remember: true,
}}
form={form} // Add this!
>
I updated my online example.
Big picture - when you want to set the value after creation, use this hook and form.setFieldsValue

Trying to make a Formik multistep with validation. How can I validate fields?

I am trying to make a field validation in a formik. The problem is that I made a multi-step form, my approach is that I made an array of components and I use them based on pages[state] and I don't know how to send props to that.
I want to validate the field to take only two digit numbers, and to show a error message if it doesn't
const SignUp = () => {
const state = useSelector((state: RootState) => state);
console.log("state", state);
const dispatch = useDispatch();
return (
<Formik
initialValues={{ firstName: "", lastName: "", email: "" }}
onSubmit={values => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500);
}}
render={({ errors, touched, isValidating }) => (
<Form>
<div>{pages[state]}</div>
<button
type="button"
onClick={() => {
dispatch(decrement());
}}
>
Prev
</button>
<button
type="button"
onClick={() => {
pages.length - 1 <= state
? console.log("No more pages")
: dispatch(increment());
}}
>
Next
</button>
</Form>
)}
/>
);
};
const Page2 = () => {
return (
<>
<h1>
What is your <span id="idealWeightText">ideal weight</span> that you
want to reach?
</h1>
<Input
name={"firstName"}
htmlFor={"firstName"}
type={"number"}
validation={validateKilograms}
/>
</>
);
};
const Input: React.FC<FieldProps> = ({
name,
onChange,
htmlFor,
type,
validation,
placeholder
}) => {
return (
<>
<label htmlFor={name}>
<Field
type={type}
name={name}
placeholder={placeholder}
validate={validation}
onChange={onChange}
/>
kg
</label>
</>
);
};
There is a library called Yup, That You can use, You will get freedom to set conditional validations.
Yup is best suited with formik for validations.
After Every step You can enable a flag and set the validation accordingly.
Also I would suggest you to use withFormik. Instead of formik because there you can make a separate file for validations and can give its path. So that will also improve your folder structure.

Is it possible use react-codemirror2 with formik?

I have a project and I use Formik and react-codemirror2, I want control the onChange in Formik but the onChange in codemirror2 don't have event...and i dont't know how to use...
let me explain better :
i have a formink:
<Formik
enableReinitialize
onSubmit={values =>
{this.submitFormDefinition(values)}}
initialValues={this.state}
>
{({ handleSubmit }) => (
<div>
<Form
onSubmit={handleSubmit}
autoComplete="off"
onChange={event => {
this.handleChange(event)
}}
>
<Field
component={CustomInputComponent}
name="name"
id="id"
multiline
/>
</Form>
</div>
)}
where I have my function handleChange:
handleChange = event => {console.log('change') ....}
an where the CustomInputComponent is my codemirror2 component:
const CustomInputComponent = ({ field, form, ...props }) => {
return (
<CodeMirror
id={props.id}
name={props.name}
value={this.state.value}
options={{
mode: 'javascript',
theme: 'default',
lineNumbers: true,
}}
{...props}
onBeforeChange={(editor, data, value) => {
console.log({ value })
this.setState({ jsonSchema: value })
}}
onChange={(editor, data, value) => {
?????????????
}}
/>
)
}
If I use another component as textField from Materia-UI it work i don't need to call on my CustomInputComponent the onChange , that is direct call by formink... because also the onChange of textField have as parameter event... but as you can see in the code the onChange of codeMirror doesn't have event...
I need to call the my handleChange and not onChange of codeMirror ...
I tried to do something like that:
<CodeMirror
onChange=form.handleChange
or use :
<CodeMirror
onKeyUp={form.handleChange}
or:
<CodeMirror
onKeyUp={(editor, event) => {
form.handleChange(event)
}}
but nothing works
my function handleChange is never call
How use react-codeMirror2 with Formik? is possible? how can I itercept the onChange of Formink?????
Formik's handleChange method expects an HTMLInput change event, and it gets the updated value from the input. Unfortunately, you don't get that convenience if you're using CodeMirror. However, CodeMirror provides the value of the component onBeforeChange, and Formik provides a setFieldValue method, which lets you imperatively set a value into state. So you can do this:
<Formik {...whateverPropsYouSet}>
(formikProps) => (
<CodeMirror
value={formikProps.values.yourDataField}
onBeforeChange={(_editor, _data, value) => {
formikProps.setFieldValue('yourDataField', value);
}
/>
)
</Formik>

Categories