I am trying to create a custom component that iterates over an array of data and display for each item an input field type radio with its according label, it works but for some reason the data is displayed three times, instead of just once for each field and I cant figure out why, I just want to render just three inputs, why does this behavior occur, am I missing something? Here is my code:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Tooltip } from '#progress/kendo-react-tooltip';
import { Form, Field, FormElement } from '#progress/kendo-react-form';
import { RadioGroup, RadioButton } from '#progress/kendo-react-inputs';
import { Error } from '#progress/kendo-react-labels';
import { Input } from '#progress/kendo-react-inputs';
const emailRegex = new RegExp(/\S+#\S+\.\S+/);
const data = [
{
label: 'Female',
value: 'female',
},
{
label: 'Male',
value: 'male',
},
{
label: 'Other',
value: 'other',
},
];
const emailValidator = (value) =>
emailRegex.test(value) ? '' : 'Please enter a valid email.';
const EmailInput = (fieldRenderProps) => {
const { validationMessage, visited, ...others } = fieldRenderProps;
return (
<div>
<Input {...others} />
{visited && validationMessage && <Error>{validationMessage}</Error>}
</div>
);
};
const Component = () => {
return (
<>
{data.map((item, index) => {
return (
<p>
<input
name="group1"
type="radio"
value={item.value}
label={item.label}
key={index}
/>
<label>{item.label}</label>
</p>
);
})}
</>
);
};
const App = () => {
const handleSubmit = (dataItem) => alert(JSON.stringify(dataItem, null, 2));
const tooltip = React.useRef(null);
return (
<Form
onSubmit={handleSubmit}
render={(formRenderProps) => (
<FormElement
style={{
maxWidth: 650,
}}
>
<fieldset className={'k-form-fieldset'}>
<legend className={'k-form-legend'}>
Please fill in the fields:
</legend>
<div className="mb-3">
<Field
name={'firstName'}
component={Input}
label={'First name'}
/>
</div>
<div className="mb-3">
<Field name={'lastName'} component={Input} label={'Last name'} />
</div>
<div className="mb-3">
<Field
name={'email'}
type={'email'}
component={EmailInput}
label={'Email'}
validator={emailValidator}
/>
</div>
</fieldset>
<div
onMouseOver={(event) =>
tooltip.current && tooltip.current.handleMouseOver(event)
}
onMouseOut={(event) =>
tooltip.current && tooltip.current.handleMouseOut(event)
}
>
<RadioGroup data={data} item={Component} />
<Tooltip
ref={tooltip}
anchorElement="target"
position="right"
openDelay={300}
parentTitle={true}
/>
</div>
<div className="k-form-buttons">
<button
type={'submit'}
className="k-button k-button-md k-rounded-md k-button-solid k-button-solid-base"
disabled={!formRenderProps.allowSubmit}
>
Submit
</button>
</div>
</FormElement>
)}
/>
);
};
ReactDOM.render(<App />, document.querySelector('my-app'));
and here is an example:
https://stackblitz.com/edit/react-rfb1ux-jpqvwy?file=app/main.jsx
<RadioGroup data={[1]} item={Component} />
RadioGroup assumes that Component element is to be rendered over data array ... so it renders like this -->
data.map(val=><Component />)
here size of data array is 3 .
or
<Component />
Radio Group will do the mapping for you. Try changing Component to:
const Component = (props) => {
return (
<p>
<input
name="group1"
type="radio"
value={props.children.props.value}
label={props.children.props.label}
key={props.children.props.label}
/>
<label>{props.children.props.label}</label>
</p>
);
};
This will then map out the items for you.
seems like this works,
const Component = ({ children: { props } }) => {
const { value, label } = { ...props };
return (
<p>
<input name="group1" type="radio" value={value} label={label} />
<label>{label}</label>
</p>
);
};
Related
I have a form and song state where I am trying to attach a new field "appleMusicId", however whenever I do this, it resets the timeDescription and sceneDescription values.
I've been trying to fix for hours and am worried I'm overlooking something simple.
As an example when I first hit Submit I get this for the console.log values
{
"episodeId": 5,
"spotifyId": "3NanY0K4okhIQzL33U5Ad8",
"name": "Boy's a liar",
"artistName": "PinkPantheress",
"timeDescription": 12,
"sceneDescription": "First song at the party."
}
If I then click the "Add Apple ID" button, the values become this on onSubmit and the time and scene removed.
{
"episodeId": 5,
"spotifyId": "3NanY0K4okhIQzL33U5Ad8",
"name": "Boy's a liar",
"artistName": "PinkPantheress",
"appleMusicId": "dummyId"
}
Component
import { TextField } from "#/components/TextField";
import { Button } from "#chakra-ui/react";
import { Formik, Form } from "formik";
import { validate } from "graphql";
import { useState } from "react";
const initialValues = {
name: "",
artistName: "",
episodeId: undefined,
movieId: undefined,
spotifyId: "",
appleMusicId: "",
sceneDescription: "",
timeDescription: undefined,
};
const AddSong = () => {
const [song, setSong] = useState(initialValues);
const submit = (values: any) => {
console.log(values, "values");
}
const addAppleId = () => {
setSong((v) => {
return {
...v,
appleMusicId: "dummyId",
};
});
};
return (
<Formik
initialValues={song}
onSubmit={(values) => submit(values)}
validationSchema={validate}
enableReinitialize={true}
>
<Form>
<TextField name={"name"} label={"Name"} type="text" />
<TextField name={"artistName"} label={"Artist Name"} type="text" />
<TextField
name={"timeDescription"}
label={"Time Description"}
type="number"
placeholder="How many minutes in, was this song played?"
/>
<TextField
name={"sceneDescription"}
label={"Scene Description"}
type="text"
/>
<Button onClick={addAppleId}>Test Add Apple Id</Button>
<Button isLoading={isLoading} type={"submit"}>
Create
</Button>
</Form>
</Formik>
)
};
TextField (if you need to see it)
export const TextField = ({ label, value, ...props }: TextFieldProps) => {
const [field, meta] = useField(props);
return (
<FormControl
isInvalid={
meta.touched && (meta.error && meta.error?.length > 0 ? true : false)
}
mb={3}
>
<FormLabel
aria-label={field.name}
htmlFor={field.name}
mb={0}
textColor={useColorModeValue('gray.700', 'gray.100')}
>
{label}
</FormLabel>
<Input
{...field}
{...props}
value={value ? value : undefined}
autoComplete="off"
borderRadius={0}
paddingLeft={2}
border={"2px solid"}
borderColor={"gray.200"}
_invalid={{ borderColor: "error-red" }}
/>
<ErrorMessage
name={field.name}
component="span"
className="text-sm pt-2 text-red-error"
/>
<Text pt={1} fontSize={'sm'} color={useColorModeValue('gray.400', 'gray.600')}>{props.footerText}</Text>
</FormControl>
);
};
Hi Bro I have send you proposal on UW Plathform . Please accept it will reolve issue
Also It would be grat if you check out my proposal.
Best
Asadbek Savronov!
It seems like the issue is happening because you are updating the song state outside of the Formik component and therefore the form values are not updating.
To fix the issue, you need to update the form values within the Formik component. You can do this by passing the setFieldValue prop to your "Add Apple ID" button and updating the form values directly within the Formik component.
Possible Solution:
const AddSong = () => {
return (
<Formik
initialValues={initialValues}
onSubmit={(values) => console.log(values)}
validationSchema={validate}
enableReinitialize={true}
>
{({ setFieldValue }) => (
<Form>
<TextField name={"name"} label={"Name"} type="text" />
<TextField name={"artistName"} label={"Artist Name"} type="text" />
<TextField
name={"timeDescription"}
label={"Time Description"}
type="number"
placeholder="How many minutes in, was this song played?"
/>
<TextField
name={"sceneDescription"}
label={"Scene Description"}
type="text"
/>
<Button onClick={() => setFieldValue("appleMusicId", "dummyId")}>
Test Add Apple Id
</Button>
<Button isLoading={isLoading} type={"submit"}>
Create
</Button>
</Form>
)}
</Formik>
);
};
no issue in your code.
I have tried to create my project and run it.
import { Formik, Form, useField, ErrorMessage, } from "formik";
// import { validate } from "graphql";
import { useState } from "react";
import {FormControl, FormLabel, Input} from '#material-ui/core'
import {useColorModeValue, Button, Text} from '#chakra-ui/react'
const TextField = ({ label, value, ...props }) => {
const [field, meta] = useField(props);
return (
<FormControl
isInvalid={
meta.touched && (meta.error && meta.error?.length > 0 ? true : false)
}
mb={3}
>
<FormLabel
aria-label={field.name}
htmlFor={field.name}
mb={0}
textColor={useColorModeValue('gray.700', 'gray.100')}
>
{label}
</FormLabel>
<Input
{...field}
{...props}
value={value ? value : undefined}
autoComplete="off"
borderRadius={0}
paddingLeft={2}
border={"2px solid"}
borderColor={"gray.200"}
_invalid={{ borderColor: "error-red" }}
/>
<ErrorMessage
name={field.name}
component="span"
className="text-sm pt-2 text-red-error"
/>
<Text pt={1} fontSize={'sm'} color={useColorModeValue('gray.400', 'gray.600')}>{props.footerText}</Text>
</FormControl>
);
};
const initialValues = {
name: "111111111",
artistName: "11111111",
appleMusicId: "",
sceneDescription: "12",
timeDescription: 1,
};
export const AddSong = () => {
const [song, setSong] = useState(initialValues);
const [isLoading, setIsLoading] = useState(true)
const submit = (values) => {
console.log(values, "values");
}
const addAppleId = () => {
setSong((v) => {
return {
...v,
appleMusicId: "dummyId",
};
});
};
return (
<Formik
initialValues={song}
onSubmit={(values) => submit(values)}
// validationSchema={validate}
enableReinitialize={true}
>
<Form>
<TextField name={"name"} label={"Name"} type="text" />
<TextField name={"artistName"} label={"Artist Name"} type="text" />
<TextField
name={"timeDescription"}
label={"Time Description"}
type="number"
placeholder="How many minutes in, was this song played?"
/>
<TextField
name={"sceneDescription"}
label={"Scene Description"}
type="text"
/>
<Button onClick={addAppleId}>Test Add Apple Id</Button>
<Button type={"submit"}>
Create
</Button>
</Form>
</Formik>
)
};
I am getting log like this:
{name: '111111111', artistName: '11111111', appleMusicId: '', sceneDescription: '12', timeDescription: 1}
and after adding dummy apple id:
{name: '111111111', artistName: '11111111', appleMusicId: 'dummyId', sceneDescription: '12', timeDescription: 1}
I am new to react and I have just started using Formik
I like how simple it makes making forms and handling forms in react.
I have created multiple custom fields using formik, I am putting the react-select field I created as an example here.
import { ErrorMessage, Field } from "formik";
import React from "react";
import Select from 'react-select'
const SelectInput = (props) => {
const { label, name, id,options, required, ...rest } = props;
const defaultOptions = [
{label : `Select ${label}`,value : ''}
]
const selectedOptions = options ? [...defaultOptions,...options] : defaultOptions
return (
<div className="mt-3">
<label htmlFor={id ? id : name}>
{label} {required && <span className="text-rose-500">*</span>}
</label>
<Field
// className="w-full"
name={name}
id={id ? id : name}
>
{(props) => {
return (
<Select
options={selectedOptions}
onChange={(val) => {
props.form.setFieldValue(name, val ? val.value : null);
}}
onClick = {(e)=>{e.stopPropagation}}
{...rest}
// I want someting like onReset here
></Select>
);
}}
</Field>
<ErrorMessage
name={name}
component="div"
className="text-xs mt-1 text-rose-500"
/>
</div>
);
};
export default SelectInput;
This is the usual code I use for submitting form as you can see I am using resetForm() method that is provided by formik, I want to attach the reseting logic in on submit method itself.
const onSubmit = async (values, onSubmitProps) => {
try {
//send request to api
onSubmitProps.resetForm()
} catch (error) {
console.log(error.response.data);
}
};
If you want to reset the selected value after the form is submitted, you need to provide a controlled value for the Select component.
The Formik Field component provides the value in the props object, so you can use it.
For example:
SelectInput.js
import { ErrorMessage, Field } from 'formik';
import React from 'react';
import Select from 'react-select';
const SelectInput = ({ label, name, id, options, required, ...rest }) => {
const defaultOptions = [{ label: `Select ${label}`, value: '' }];
const selectedOptions = options ? [...defaultOptions, ...options] : defaultOptions;
return (
<div className='mt-3'>
<label htmlFor={id ? id : name}>
{label} {required && <span className='text-rose-500'>*</span>}
</label>
<Field
// className="w-full"
name={name}
id={id ? id : name}
>
{({
field: { value },
form: { setFieldValue },
}) => {
return (
<Select
{...rest}
options={selectedOptions}
onChange={(val) => setFieldValue(name, val ? val : null)}
onClick={(e) => e.stopPropagation()}
value={value}
/>
);
}}
</Field>
<ErrorMessage name={name} component='div' className='text-xs mt-1 text-rose-500' />
</div>
);
};
export default SelectInput;
and Form.js
import { Formik, Form } from 'formik';
import SelectInput from './SelectInput';
function App() {
return (
<Formik
initialValues={{
firstName: '',
}}
onSubmit={async (values, { resetForm }) => {
console.log({ values });
resetForm();
}}
>
<Form>
<SelectInput
name='firstName'
label='First Name'
options={[{ label: 'Sam', value: 'Sam' }]}
/>
<button type='submit'>Submit</button>
</Form>
</Formik>
);
}
export default App;
Therefore, if you click the Submit button, value in the Select component will be reset.
You can also make a useRef hook to the Fromik component and then reset the form within the reset function without adding it as a parameter to the function.
https://www.w3schools.com/react/react_useref.asp
It's one of the really nice hooks you'll learn as you progress through React :)
So if I understood you correctly you want to reset a specif field value onSubmit rather than resetting the whole form, that's exactly what you can achieve using actions.resetForm().
Note: If nextState is specified, Formik will set nextState.values as the new "initial state" and use the related values of nextState to update the form's initialValues as well as initialTouched, initialStatus, initialErrors. This is useful for altering the initial state (i.e. "base") of the form after changes have been made.
You can check this in more detail here.
And here is an example of resetting a specific field using resetForm() whereby you can see as you input name, email and upon submit only email field will get empty using resetForm.
import "./styles.css";
import React from "react";
import { Formik } from "formik";
const initialState = {
name: "",
email: ""
};
const App = () => (
<div>
<h1>My Form</h1>
<Formik
initialValues={initialState}
onSubmit={(values, actions) => {
console.log(values, "values");
actions.resetForm({
values: {
email: initialState.email
}
});
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.name}
name="name"
/>
<br />
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.email}
name="email"
/>
<br />
<br />
{props.errors.name && <div id="feedback">{props.errors.name}</div>}
<button type="submit">Submit</button>
</form>
)}
</Formik>
</div>
);
export default App;
Is it possible to substitute the value from useState with the one coming from input?
Or is there a way to do this using dispatch?
I have tried many ways, but none of them work.
const renderInput = ({
input,
label,
type,
meta: { asyncValidating, touched, error },
}) => {
const [value, setValue] = useState('default state');
const onChange = event => {
setValue(event.target.value);
// + some logic here
};
return (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} value={value} onChange={onChange} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
};
const SelectingFormValuesForm = props => {
const { type, handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<label>Name</label>
<div>
<Field
name="name"
component={renderInput}
type="text"
placeholder="Dish name..."
/>
</div>
</div>
</form>
);
};
SelectingFormValuesForm = reduxForm({
form: 'selectingFormValues',
validate,
asyncValidate,
// asyncBlurFields: ['name'],
})(SelectingFormValuesForm);
export default SelectingFormValuesForm;
This way, unfortunately, the value sent to the submit remains empty.
I have an API which with an object and encapsulates errors related with each inputField within it, for example:
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"firstname": [
"Invalid firstname",
"Firstname must be at least 2 characters long"
],
"companyname": ["company name is a required field"]
}
}
based on the errors, I need to display the error right below the input element, for which the code looks like this:
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
<Field name='companyname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Field name='firstname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Translate
content='button.register'
className='btn btn-primary btn-form'
type='submit'
component='button'
/>
</form>
</div>
);}}
The inputField:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={<Translate content={error} />}
/>
</div>
);
};
and finally;
import React, { Component } from "react";
class InputFieldError extends Component {
render() {
return <font color="red">{this.props.touched && this.props.error}</font>;
}
}
export default InputFieldError;
If I validate simply with validate={validation.required} the error property is attached to the correct input field and I can render the error div using InputFieldError right below it.
I am mapping all the errors back from the API response on to props like this:
function mapStateToProps(state) {
return { errorMessage: state.errors, countries: state.countries };
}
which means I can print every error that I encounter at any place like this:
{this.props.errorMessage
? displayServerErrors(this.props.errorMessage)
: null}
rendering all at same place by simply going through each property on errorMessage is easy.
Now when I try to assign the errors back from the API ("errors": {"firstname": []} is linked with Field name="firstname" and so on...), I cannot find a way to attach the error in "firstname" property to the correct InputFieldError component in Field name="firstname"
I hope the question is clear enough, to summarise it I am trying to render error I got from API to their respective input element.
Try to pass errors.firstname to field inner component like this
<Field name='companyname' type='text' component={<inputField apiErrors={this.props.errorMessage.errors.firstname || null} {...props}/>} className='form-control' validate={validation.required} />
and then your inputField will be:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
apiErrors,
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={apiErrors ? apiErrors : <Translate content={error} />}
/>
</div>
);
};
and:
class InputFieldError extends Component {
render() {
const errors = this.props.error
let errorContent;
if (Array.isArray(errors)) {
errorContent = '<ul>'
errors.map(error, idx => errorContent+= <li key={idx}><Translate content={error}/></li>)
errorContent += '</ul>'
} else {
errorContent = errors
}
return <font color="red">{this.props.touched && errorContent}</font>;
}
}
You can also add the main error at the top of your form
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
{this.props.errorMessage.message &&
<Alert>
<Translate content={this.props.errorMessage.message}/>
</Alert>
}
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
...
I am using Formik to create a form on a website. In this form I have a set of tags which I want to be checkable with individual checkboxes, but keep them in an array tags. Formik claims to be able to do so by using the same name attribute on the Field elements, but I can not get it to work.
I am using this code example as an reference.
Here is my code:
import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field } from "formik";
import "./styles.css";
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ProjectsForm />
</div>
);
}
const ProjectsForm = props => (
<Formik
initialValues={{
search: "",
tags: []
}}
onSubmit={values => {
console.log(values);
}}
validate={values => {
console.log(values);
}}
validateOnChange={true}
>
{({ isSubmitting, getFieldProps, handleChange, handleBlur, values }) => (
<Form>
<Field type="text" name="search" />
<label>
<Field type="checkbox" name="tags" value="one" />
One
</label>
<label>
<Field type="checkbox" name="tags" value="two" />
Two
</label>
<label>
<Field type="checkbox" name="tags" value="three" />
Three
</label>
</Form>
)}
</Formik>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Also on CodeSandbox
I am expecting to receive thought the console.log(values) to display something like:
Object {search: "", tags: ['one', 'three']}
or
Object {search: "", tags: ['one': true, 'two': false, 'three': true]}
Hope there is a simple thing I am missing to add the Formik checkbox group functionality, as it claims to be possible.
It's pretty easy to accomplish using the FieldArray component.
Place your values in an array, something like this:
const tagCollection = [
{ value: "one", label: "One" },
{ value: "two", label: "Two" },
{ value: "three", label: "Three" }
];
And then use the FieldArray like this:
<FieldArray
name="tags"
render={arrayHelpers => (
<div>
{tagCollection.map(tag => (
<label key={tag.value}>
<input
name="tags"
type="checkbox"
value={tag}
checked={values.tags.includes(tag.value)}
onChange={e => {
if (e.target.checked) {
arrayHelpers.push(tag.value);
} else {
const idx = values.tags.indexOf(tag.value);
arrayHelpers.remove(idx);
}
}}
/>
<span>{tag.label}</span>
</label>
))}
</div>
)}
/>
Working sandbox
Update:
You can also do this by writing your own component.
const MyCheckbox = ({ field, form, label, ...rest }) => {
const { name, value: formikValue } = field;
const { setFieldValue } = form;
const handleChange = event => {
const values = formikValue || [];
const index = values.indexOf(rest.value);
if (index === -1) {
values.push(rest.value);
} else {
values.splice(index, 1);
}
setFieldValue(name, values);
};
return (
<label>
<input
type="checkbox"
onChange={handleChange}
checked={formikValue.indexOf(rest.value) !== -1}
{...rest}
/>
<span>{label}</span>
</label>
);
};
// And using it like this:
<Field component={MyCheckbox} name="tags" value="one" label="One" />
<Field component={MyCheckbox} name="tags" value="two" label="Two" />
<Field component={MyCheckbox} name="tags" value="three" label="Three" />
With Formik 2 without any Formik specific components:
import React from "react";
import { useFormik } from "formik";
export default function App() {
return <ProjectsForm></ProjectsForm>;
}
const tags = ["one", "two", "three"];
const ProjectsForm = () => {
const formik = useFormik({
enableReinitialize: true,
initialValues: {
tags: []
},
onSubmit: (values) => {
console.log(values);
}
});
const handleChange = (e) => {
const { checked, name } = e.target;
if (checked) {
formik.setFieldValue("tags", [...formik.values.tags, name]);
} else {
formik.setFieldValue(
"tags",
formik.values.tags.filter((v) => v !== name)
);
}
};
return (
<form onSubmit={formik.handleSubmit}>
{tags.map((tag) => (
<div key={tag}>
<input
id={tag}
type="checkbox"
name={tag}
checked={formik.values.tags.includes(tag)}
onChange={handleChange}
/>
<label htmlFor={tag}>{tag}</label>
</div>
))}
<button type="submit">Submit</button>
</form>
);
};
I started by creating a simple array of tags.
Then I used useFormik hook to bind the form for an array of checkboxes. Whether a checkbox is checked, depends on whether it's in the tags array.
Hence: formik.values.tags.includes(tag)
I created a custom change handling function to set or remove a tag from Formik values depending on whether a checkbox is checked or not.
Full working Sandbox example