I'm using a custom component in formik field, and need to call a function upon text change inside function (in order to get all matching hints related to input)
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
key="assignees"
className="input-fields"
placeholder="Type names of assignees to start getting suggestions"
onChange={(e) => {
form.setFieldValue(field.name, e.target.value);
getMatching(field.value);
}}
{...props}
{...form}
// {...field}
/>
);
}}
/>
The matching function that's being called in upper code is as below. It updates a state and that state is then being showed inside span in a formik field.
const getMatching = (keyword) => {
for (const item of users) {
if (keyword !== "" && item.name.startsWith(keyword)) {
setMatching(item.name);
return;
} else {
setMatching(undefined);
}
}
};
Expected Behaviour
Formik input doesn't lose focus and retains all the input texts I added upon state change or rerender
Actual Behaviour
Input component loses focus and field is reset whenever a state is set by getMatching function
EDIT- CODE SANDBOX LINK
https://codesandbox.io/s/wizardly-merkle-w191l
A few things:
no need to store matching in state, it can be derived from looking at the assignee textbox value and the list of users
the assignee textbox (I'm calling it searchAssignee) and the list of selected assignees (assignees) are essentially two different inputs, so must be separate Formik inputs
separating the two above inputs fixes the focus issue
The whole idea of formik is that it removes the need to store input values in state. I removed the unnecessary state and separated the above inputs.
Working Example
const getMatching = (users, keyword) => {
if (!keyword) return null;
const match = users.find(({ name }) => name.startsWith(keyword));
if (!match) return null;
return match.name;
};
const FormContainer = ({ users }) => {
return (
<Formik
initialValues={{
summary: "",
description: "",
searchAssignee: "",
assignees: []
}}
onSubmit={async (values) => {
console.log(values);
}}
>
<Form>
<Row>
<Col>
<div className="textcolor">Summary</div>
<Field
id="summary"
name="summary"
placeholder="Add a short summary for your task"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
className="input-fields"
{...props}
// {...form}
{...field}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Description</div>
<Field
id="description"
name="description"
placeholder="Description"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<ReactQuill
theme="snow"
value={field.value}
onChange={field.onChange(field.name)}
style={{ minHeight: "5rem" }}
{...field}
{...props}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Assignees</div>
<Col className="assignee-wrapper d-flex">
<div className="d-flex">
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => (
<>
{field.value.map((item) => (
<div
className="assignee-tag d-flex"
onClick={() => {
const newAssignees = field.value.filter(
(val) => val !== item
);
form.setFieldValue(field.name, newAssignees);
}}
>
<div>{item}</div>
<GrFormClose />
</div>
))}
</>
)}
/>
</div>
<div className="d-flex col">
<Field
id="searchAssignee"
name="searchAssignee"
placeholder="Type names of assignees to start getting suggestions"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
const suggestion = getMatching(users, field.value);
return (
<>
<input
className="input-fields"
{...props}
{...form}
{...field}
/>
{suggestion && (
<span
className="text-nowrap"
onClick={() => {
if (!form.values.assignees.includes(suggestion)) {
form.setFieldValue("assignees", [
...form.values.assignees,
suggestion
]);
}
}}
>
{suggestion}
</span>
)}
</>
);
}}
/>
</div>
</Col>
</Col>
</Row>
<div className="mt-2 float-end">
<button className="btn btn-dark btn-sm" type="submit">
Add
</button>
<button className="btn btn-light btn-sm ms-2" type="reset">
Cancel
</button>
</div>
</Form>
</Formik>
);
};
const App = ({ isModalVisible, setModalVisible }) => {
const [users, setUsers] = useState([
{ name: "Hello World" },
{ name: "Foo Bar" }
]);
return (
<Modal show={true}>
<Modal.Body>
<Row>
<div>
<GrFormClose
className="float-end"
onClick={() => setModalVisible(false)}
style={{ cursor: "pointer" }}
/>
</div>
<div>
<FormContainer
users={users}
/>
</div>
</Row>
</Modal.Body>
</Modal>
);
};
Related
The form input campaignid is automatically filled in when the page is loaded.
I've added onChange and onBlur to name input to prevent default, however when you click on the submit button, no action is taken.
I'm using a third party script to get URL parameters and insert them into campaignid value.
import * as React from "react";
import Script from "next/script";
import { Field, Form, Formik } from "formik";
import {
Button,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
Input,
} from "#chakra-ui/react";
export default function FormikForm() {
function validateName(value) {
let error;
if (!value) {
error = "Type a valid name.";
}
return error;
}
return (
<>
<Script src="https://cdn.jsdelivr.net/gh/gkogan/sup-save-url-parameters/sup.min.js" />
<Formik
initialValues={{
name: "",
}}
onSubmit={(values) => {
setTimeout(() => {
fetch(`https://hooks.zapier.com/hooks/catch/3660927/bte5w75/`, {
method: "POST",
body: JSON.stringify(values, null, 2),
}),
3000;
});
}}
>
{(props) => (
<Flex>
<Form id="hero">
<Field name="name" validate={validateName}>
{({ field, form }) => (
<FormControl
isInvalid={form.errors.name && form.touched.name}
>
<FormLabel htmlFor="name">Nome</FormLabel>
<Input
{...field}
id="name"
onChange={(e) => {
e.preventDefault();
}}
onBlur={(e) => {
e.preventDefault();
}}
/>
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="campaignid">
{({ field }) => (
<FormControl isReadOnly>
<Input {...field} type="hidden" id="campaignid" />
</FormControl>
)}
</Field>
<Button
id="submited"
isLoading={props.isSubmitting}
type="submit"
>
Submit
</Button>
</Form>
</Flex>
)}
</Formik>
</>
);
}
In react native I want to make a dynamic controller component. But i cant access errors with it. I using "react-hook-form" for form elements. So Its my component :
const {
control,
handleSubmit,
formState: {errors},
setValue,
} = useForm();
const DynamicController = ({req, pattern, name, label}) => {
return (
<>
<Text style={[t.textBase]}>{label}</Text>
<Controller
control={control}
defaultValue=""
rules={{
required: {
value: true,
message: 'Bu alan boş bırakılamaz!',
},
}}
render={({field: {onChange, onBlur, value}}) => (
<Input
errorText={errors[name].message}
error={errors[name]}
onBlur={onBlur}
placeholder={label}
onChangeText={onChange}
value={value}
/>
)}
name={name}
/>
</>
);
};
My Input Component is basicly simple input. My problem is when i give error name like that example i cant access errors.
Its how i use my component :
<DynamicController
label="Email"
name="Email"
pattern={true}
req={true}
/>
When i dont fill the element and log the submit its not showing any error. Its simple passing validate. So what can i do where do i make wrong ? thank you for answerings!!!
Is your Input a custom wrapper? If not, a better way do this using react-hook-form would be:
const {
control,
handleSubmit,
formState: {errors},
setValue,
} = useForm(
defaultValues: {
firstName: '', // form fields should be populated here so that the error can be displayed appropriately
lastName: ''
}
);
const DynamicController = ({req, pattern, name, label}) => {
return (
<>
<Text style={[t.textBase]}>{label}</Text>
<Controller
control={control}
defaultValue=""
rules={{
required: {
value: true,
message: 'Bu alan boş bırakılamaz!',
},
}}
render={({field: {onChange, onBlur, value}}) => (
<Input
onBlur={onBlur}
placeholder={label}
onChangeText={onChange}
value={value}
/>
)}
name={name}
/>
{errors[name] && <Text>This is required.</Text>}
</>
);
};
I want to trigger formik errors and touched whenever the input is clicked and the value is not correct .
I pass formik props to the input component like this :
const initialValues = {
title: ''
};
const validationSchema = yup.object({
title: yup.string().max(50, 'less than 50 words !!').required('required !!')
});
function Add() {
<Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
{(props) => {
return (
<Form>
<AddTitle props={props} />
</Form>
);
}}
</Formik>
);
}
Here I'm trying to display error message whenever the input is touched and there is an error with it like this :
import { Input } from 'antd';
function AddTitle(props) {
console.log(props.props);
return (
<Field name="title">
{() => {
return (
<Input
onChange={(e) => {
props.props.setFieldValue('title', e.target.value)
}}
/>
);
}}
</Field>
<ErrorMessage name="title" />
<P>
{props.props.touched.title && props.props.errors.title && props.props.errors.title}
</P>
</React.Fragment>
);
}
But ErrorMessage and the paragraph below it doesn't work when the input is touched and empty .
In console log it shows that input doesn't handle formik touched method and it only triggers the error for it :
touched:
__proto__: Object
errors:
title: "less than 50 words !"
__proto__: Object
How can I use ErrorMessage properly while passing in formik props to a component and using a third library for inputs ?
Fixed the issue by adding the onBlur to the input and ErrorMessage is working fine :
<Field name="title">
{() => {
return (
<Input
onBlur={() => props.props.setFieldTouched('title')}
onChange={(e) => {
props.props.setFieldValue('title', e.target.value);
}}
/>
);
}}
</Field>
<P class="mt-2 text-danger">
<ErrorMessage name="title" />
</P>
I'm using Formik for validating the fields before creating an entity. There is a Select which opens a dropdown and the user must choose one option from there.
I get an error saying that setFieldValue is not defined but where I've researched before I didn't find any definition of this method, it is just used like that so I don't know why is this happening.
This is my code:
import React from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
industry: '',
};
}
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
handleSubmit = values => {
// ...
};
render() {
const nameOptions = [
{ text: 'Services', value: 'Services' },
{ text: 'Retail', value: 'Retail' },
];
const initialValues = {
industry: '',
};
const requiredErrorMessage = 'This field is required';
const validationSchema = Yup.object({
industry: Yup.string().required(requiredErrorMessage),
});
return (
<div>
<div>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={e => setFieldValue('industry', e.target.value)} // here it is
/>
<div>
{touched.industry && errors.industry
? errors.industry
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</div>
);
}
}
The error says: 'setFieldValue' is not defined no-undef - it is from ESLint. How can be this solved?
setFieldValue is accessible as one of the props in Formik.
I tried it on CodeSandbox, apparently, the first parameter in the onChange prop returns the event handler while the second one returns the value selected onChange={(e, selected) => setFieldValue("industry", selected.value) }
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values) => this.handleSubmit(values)}
>
{({ errors, touched, setFieldValue }) => (
//define setFieldValue
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
id="industry" // remove onBlur warning
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={(e, selected) =>
setFieldValue("industry", selected.value)
}
/>
<div>
{touched.industry && errors.industry
? errors.industry
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
try calling it using this structure
onChange = {v => formik.setFieldValue('field', v)}
<Formik
htmlFor="amazing"
initialValues={initialValues}
value={{industry:this.state.industry}}
validationSchema={validationSchema}
onSubmit={(values) => this.handleSubmit(values)}
>
{({ errors, touched }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={(e) =>
this.setState({
industry: e.target.value,
})
} // here it is
/>
<div>
{touched.industry && errors.industry ? errors.industry : null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>;
Replace your onChange by onChange={this.onChange('industry', e.target.value)}
and in your constructor add this line this.onChange = this.onChange.bind(this).
your onChange methode will be:
onChange(e, { name, value }){
this.setState({ [name]: value });
}
I am attempting to grab the value of an input form on React Bootstrap but the obvious way doesn't seem to be working.
This is the form field component:
const FormField = ({
type,
label,
value,
onChange,
controlId,
placeholder,
}) => (
<Form.Group as={Row} controlId={controlId}>
<Form.Label column sm={4}>
{label}
</Form.Label>
<Col sm={8}>
<Form.Control
type={type}
value={value}
onChange={() => onChange(value)}
placeholder={placeholder}
/>
</Col>
</Form.Group>
);
And here is the parent where I call that component above:
class FormFieldsGroup extends Component {
state = {
firstName: '',
};
render() {
const { firstName } = this.state;
return (
<FormField
type="text"
label="First Name"
controlId="firstName"
placeholder="First Name"
value={firstName}
onChange={firstName => this.setState({ firstName })}
/>
)
}
}
It only returns an empty string.
What can I do to store the value of that input in a local component state?
value={firstName}
must be
value={this.state.firstName}
because your value is changing when you type
Can you try onChange={onChange} in Form.Control instead of onChange={() => onChange(value)}
Also check whether it returns value direct or it returns event if returning event use event.target.value