We have our own input components (like Checkbox, Textbox, or even CurrencyInput component)
We are now using Formik. So we replaced all <input... with <Field... in our components, eg.
const Checkbox = (props) => {
...
return (
<div className={myClass1}>
<Field type='checkbox' name={props.name} className={myClass2} ... />
<label ...>{props.label}</label>
</div>
)
};
Now the problem is, we can't have a standalone Checkbox outside a form anymore (eg. for an on-screen-only option). It will throw:
this.props.formik.registerField is not a function
We feel this is a dealbreaker. But before we ditch Formik and write our own form validation logics, I wonder if anyone else are having this dependency issue.
Is there really no way of rendering Formik Field outside Formik?
The Field component is what connects a form field to the Formik state. It uses context under the hood; Formik is the context provider and Field is the context consumer. Field is tied to Formik and has no use outside of it. For your use case where you want to render form fields that are sometimes connected to Formik and sometimes not, I would export two different components:
The base Checkbox component that has nothing to do with Formik. It should just use a normal input
A Field wrapper around that Checkbox component
While the Field component can take a type, causing it to render the corresponding input, it can also take a render prop to render whatever you want, and it is passed all of the state Formik manages for that field.
For example, your Checkbox and CheckboxField components could looks something like this:
const Checkbox = (props) => {
...
return (
<div className={myClass1}>
<input type='checkbox' checked={props.checked} onChange={props.onChange} />
<label ...>{props.label}</label>
</div>
)
};
const CheckboxField = (props) => {
return (
<Field name={props.name}>
{(field) => <Checkbox label={props.label} {...field} />}
</Field>
)
}
Now you use have two components that render exactly the same, but one is meant to be used within a Formik form (CheckboxField) and the other can be used anywhere (Checkbox).
Related
EDIT: solved by a react-hook-form module update.
Problem
react-hook-form does not update form input state dirty, touched on custom components where name is dynamically passed to register. Specifically, field array inputs where the name is something like ingredients.0.description will track the input value but not the input state. Simple names like question or name seem to behave correctly.
Reproduction of the bug on this CodeSandbox.io environment.
Steps to reproduce:
Type or change the value in the "name" field (on the first line)
Type or change the value in any of the "description" fields (the field array)
Open react-hook-form DevTools (the pink icon in the bottom right of the browser)
See that the ingredients fields did not update dirty or touched values despite not matching defaultValue and having blurred off the input
See that the name field shows dirty: true, touched: true correctly
Context
I'm using react-hook-form and running into problems when integrating it with a custom uncontrolled component.
In most (all?) of the examples I see, the register function is either applied to a native HTML Input Element as one or the other:
<input {...register("field_name")} />
<input ref={register} name="field_name" />
Because I have some custom input styling, I'd like to make reusable components that integrate into react-hook-form, and though I could do something like:
// Input.tsx
export function Input(props) {
type Props = {
type: string;
name: string;
};
export function Input(props: Props) {
const { type, name } = props;
const { register, getValues, formState } = useFormContext();
return <input type={type} {...register(name)} />;
}
// App.tsx
<FormProvider {...methods} >
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="field_name" type="text" />
</form>
<FormProvider/>
Questions
Am I using register incorrectly?
Can register take a name dynamically or must it be a static string? Is my field array name description malformed?
Do uncontrolled component methods need to be applied directly to an <input /> or is it possible to wrap with a custom component and pass the props? What's the most idiomatic way to do this with react-hook-form?
How can I modify the code in my CodeSandbox to correctly reflect all field inputs' states?
I have a form in which all input, select tags are separate components and each component but the submit button is in the form it self like-
<form>
<InputComp1 />
<InputComp2 />
<Select1 />
<Select2 />
<button type='submit' value='Register'/>
</form>
So how do I collect all state from various components and when user clicks on the submit the values get submitted.?
Is this approach while dealing with forms right? or should I manage state of all tags in the same component?
Manage the state of all inputs/selects in this component. You can pass values and handler functions to the inputs using props.
There is no "right" approach, the answer depends on the context.
You can have form as a controlled component, where you manage the state of all tags (while passing callbacks down the tree) as you suggested and as mentioned in docs.
Or, you can have it as uncontrolled component, for example:
const Input = ({ name }) => {
return <input name={name} />;
};
const Component = () => {
return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
const data = new FormData(e.target);
const entries = data.entries();
for (let entry of entries) {
console.log(entry);
}
}}
>
<Input name="username" />
<Input name="email" />
<input type="submit" value="Submit" />
</form>
</>
);
};
See controlled vs uncontrolled components.
Yes you should manage the state in the parent component itself and pass the onchange handler and value of that field as props inside the child components to update the value of the form fields.
Solution #1 would be "manage state react way". With this you should store state you need to share between components somewhere in their common ancestor. In your case it would be component that holds Form
Solution #2 applicable only if you use real form and form controls. Handle 'submit' event from the form and get all you need to submit from form data.
Solution #3 applicable only if you use some sort of "state manager". Follow instructions and best practices of the library you use.
In some cases you can mix and match that solutions. For example I still recommend to handle 'submit' event on form regardless of solution.
there is a concept of state lifting in react:
create a controlled form here and for every child, component pass a function to get the data from child components to parent one. by doing this you can submit all the values once.
here is the example
import React, {useState} from 'react';
const ChildInput = ({onChange, id}) => {
return(
<input
key={id}
type="text"
placeholder="enter name"
onChange={onChange}
/>
)
}
const Parent = () => {
const [name, setName] = useState('');
const onSubmit =(e)=>{
e.preventDefault();
// append your all data here just like child component
data = {name}
}
return(
<form onSubmit={onSubbmit}>
<ChildInput onChange={()=>setName(e.target.value)} id="name" />
<button type="submit" value="submit"/>
</form>
)}
for more information check this one: https://reactjs.org/docs/glossary.html#controlled-vs-uncontrolled-components
Using ant design input and forms, I am having trouble using intialValues when a component is referenced in. In this case, I am trying to pass in CustomInput.js, a ant design Input, into my form as <Component/>. Everything works except the initialValues.
Any ideas what to do to make intialValues work?
https://codesandbox.io/s/initialvalues-forked-keftp?file=/index.js:382-414
You have to pass the props from your custom component down to Input, specifically the value prop:
function CustomInput(
{ field, value = "", onChange = (e) => {}, disabled, ...rest },
ref
) {
return (
<Input
ref={ref}
value={value}
onChange={onChange}
disabled={disabled}
{...rest}
/>
);
}
DEMO
Note: I'm unsure as to what your field prop is. Since it's not a supported prop as part of the component's API, I left it out.
I am using redux-form to submit form values as JSON object to POST service. But redux-form fields(<Field />) doesn't support events like onChange , onBlur. So I want to use the default components like <select><option></select>, <input type="text" />, but if I use the plain html components, events like onChange will work. But here I am unable to submit the form values as object to service without redux-form. Please guide...
Thanks,
Shash
redux-form does support it with custom components passed to <Field />.
One of the way is this (taken from the docs)
A component
This can be any component class that you have written or have imported
from a third party library.
// MyCustomInput.js
import React, { Component } from 'react'
class MyCustomInput extends Component {
render() {
const { value, onChange } = this.props
return (
<div>
<span>The current value is {value}.</span>
<button type="button" onClick={() => onChange(value + 1)}>Inc</button>
<button type="button" onClick={() => onChange(value - 1)}>Dec</button>
</div>
)
}
}
Then, somewhere in your form...
import MyCustomInput from './MyCustomInput'
<Field component={MyCustomInput}/>
It would be much easier for you to just make redux-form submit your values, since you're already using it.
Docs on Field
given the fact in the example http://redux-form.com/6.0.5/examples/fieldArrays/. All the renderField.. functions are outside of the React Class. Hence how am i suppose to use react state or props to determine whether i want to hide or show a Field?
What I'm trying to do is to have a button to manipulate a state to display 'block' or 'none' for a Field. Can someone guide me? I tried to put the renderField variable inside the render of the React class, however this result in bugs.
All the props which are passed to Field are accessible to component props.
<Field
name={foo}
type="text"
component={TextField}
displayBlock={displayBlock}
/>
const TextField = props => {
if(props.displayBlock) {
...
}
return (
<div>
<input {...props.input} />
</div>
);
};
Thanks to Runaground who suggested an answer to me. I came to realized that I can pass in state as props to the FieldArray, such as
<FieldArray name="histories" component={renderHistories} openClose={this.state.openClose}/>
This allow me to utilize an array of state from this.state.openClose, to control which field i would like to hide or show.
<Field
name={`${histories}.details`}
type="text"
component={renderField}
style={{display: openClose[index] ? 'block' : 'none'}}
/>
However, even though I am able to control the display of the Field, the field array does not get rerendered.
Hence, I have to add on two functions
fields.push({});
setTimeout(()=>{
fields.pop();
}, 1);
that is taken from http://redux-form.com/6.0.5/docs/api/FieldArray.md/, to actually rerender the fieldarray whenever i hide or show the field. Hopefully there is a more elegant way to do this as the fieldarray does flicker.