How to set react hook form default values when using Controller with custom components - reactjs

What I'm trying to do:
Provide default values to my custom controlled rhf components and have them update like an rhf uncontrolled component.
Better understand how Controller sets default values so I can troubleshoot.
My problem:
I can only set default values with a rhf uncontrolled component as shown in the docs. For example, by creating a defaultValues object with each input's default value then calling reset({...defaultValues}) in a useEffect on page load.
I don't understand how rhf passes/sets default values with Controller
Examples:
Codesandbox here
// Parent form component (RegistrationForm.tsx)
// set up form default values
const defaultValues = {
standardRadio: "mint",
registerRadio: "supercross"
};
// fill in default values on load
useEffect(() => {
reset({ ...defaultValues });
}, []);
// DOESN'T WORK: Can't set the default value of WrapperRadioGroup
<WrapperRadioGroup
name="standardRadio"
groupLabel="This is a radio group label"
control={control}
style="standard"
defaultValue=" "
options={[
{ value: "orange", label: "Orange ice cream" },
{ value: "mint", label: "Mint ice cream" },
{ value: "chocolate", label: "Chocolate ice cream" },
{ value: "vanilla", label: "Vanilla ice cream" }
]}
/>
// Default value set properly using the following uncontrolled component
<input
{...register("registerRadio")}
id="registerRadio1"
type="radio"
value="motogp"
/>
<label htmlFor="registerRadio1">Moto GP</label>
...
<input
{...register("registerRadio")}
id="registerRadio4"
type="radio"
value="islemantt"
/>
<label htmlFor="registerRadio4">Isle of Man TT</label>

Figured it out. When using Controller there's no need to pass a defaultValue prop on the parent component as doing so will override the value used by RHF's Controller. RHF takes the value you set up in your defaultValues object and then make that the current value for the input.
Example:
Set the default values for all of your inputs via an object. (Key: input's name value: input default value).
const defaultValues = {
standardRadio: "mint",
registerRadio: "supercross"
}
Use reset() to update the form inputs. See documentation.
useEffect(() => {
reset({ ...defaultValues });
}, []);
Don't pass a defaultValue prop to the controlled component. If you do it will override RHF Controller's value.
<WrapperRadioGroup
name="standardRadio"
groupLabel="This is a radio group label"
control={control}
style="standard"
=> defaultValue=" " // !! DON'T PASS A DEFAULTVALUE PROP. OVERRIDES RHF's CONTROLLER
options={[
{ value: "orange", label: "Orange ice cream" },
{ value: "mint", label: "Mint ice cream" },
{ value: "chocolate", label: "Chocolate ice cream" },
{ value: "vanilla", label: "Vanilla ice cream" }
]}
/>
If you need the default value in the input's controlled component then access it via props.value

Related

Is there a way to set defaultValues in a child component using react hook form with useFieldArray hook - not using the useForm hook?

Problem
I want to implement key/value pairs of input fields in a form that can be added by a user.
See animated gif on dynamic fields.
In addition, I want to display saved data when the user has submitted the form and the page is displayed again.
See animated gif on displaying saved dynamic fields.
Preconditions
I'm using react-hook-form V7 (RHF) and its useFieldArray hook.
As I use Material-UI I have to use controlled components.
Working solution
In a simplified app, I have a parent component using the useForm hook and two child components, one for the purpose of demonstration holding normal form fields and an <ArrayFields /> component holding the array fields.
Yesterday I learned by this answer that one way to do it is to set the defaultValues object in the parent's useForm hook like this:
const methods = useForm({
defaultValues: {
email: "john.smith#example.com",
firstName: "John",
lastName: "Smith",
systemRole: "Admin",
envRoles: [ // <-- saved dynamic fields
{ envName: "foo1", envRole: "bar1" },
{ envName: "foo2", envRole: "bar2" }
]
}
});
Here you can see a codesandbox of this working solution.
Question
Nonetheless, I am wondering if it isn't possible to set the defaultValues in the <ArrayFields /> child component?
useFormContext approach
For example like this using the useFormContext hook:
//
const ArrayFields = ({ fieldset }) => {
const { control, getValues } = useFormContext({
defaultValues: {
envRoles: [
{ envName: "foo1", envRole: "bar1" },
{ envName: "foo2", envRole: "bar2" }
]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "envRoles"
});
...
}
But this is not displaying the saved fields at all
See here a codesandbox version of the useFormContext approach
Props approach
Next, I tried to pass the fields (alias envRoles) as props and set the defaultValues to the Controller directly
// index.js:30
<ArrayFields
fieldset={[
{ envName: "foo1", envRole: "bar1" },
{ envName: "foo2", envRole: "bar2" }
]}
/>
// ArrayFields.js:35
<Controller
render={({ field }) => <input {...field} />}
defaultValue={item.envName} {/* <-- defaultValue on Controller */}
name={`envRoles[${index}].envName`}
control={control}
/>
This displays the defaultValues
but does not work when clicking on the add or delete button
See this codesandbox of the props approach
Question again
So is it really true that RHF does not allow handling all things that matter to the component inside this component?
Thanks in advance for your hints.
I think I found the answer. 🙊
In the documentation of useFieldArray you'll find the replace method.
replace (obj: object[]) => void Replace the entire field array values.
So using a useEffect hook it's finally easy.
useEffect(() => {
replace(fieldset);
}, [fieldset, replace]);
In this codesandbox you will find the example that finally works with setting the defaultValues in the child component.

Access selected value from react's Select Component

Hello dear community,
I want to create a very basic website for my school assignment.
I have a dropdown menu with a Select Component as its implementation. I need to access the selected value, which is a currency in my case, in order to update the information displayed on the page once a currency has been selected.
I am kind of frustrated since I wasn't able to find a helpfull solution to my relatively basic problem (I think it's basic :D)
My component class here:
import { Component } from "react";
import Select from 'react-select';
interface DropdownMenuProps {
values: [{}]
defaultValue: string
}
interface DropdownMenuState { }
/**
* Represents a Dropdown Menu
*/
export default class DropdownMenu extends Component<DropdownMenuProps, DropdownMenuState> {
render() {
return (
<div style={{ width: '120px' }}>
<Select id="dropdown-menu"
placeholder={this.props.defaultValue}
options={this.props.values}
// getOptionValue={(option) => option.value}
// getOptionLabel={(option) => option.label}
/>
</div>
)
}
}
This is how I create a dropdown menu compent:
<DropdownMenu defaultValue="Currency" values={[{ label: "EUR", value: "EUR" }, { label: "GBP", value: "GBP" }, { label: "USD", value: "USD" }]} ></DropdownMenu>
I'm glad for any tips :)
You have to use the onChange prop of react-select:
<select
id="dropdown-menu"
onChange={handleSelectChange}
>
and in this function you could handle the changes:
const handleSelectChange = (selectedVal) => console.log(selectedVal)
I recommend you using directly onChange and save it into a state:
1st:
Create state with the name you want:
const [selectValue, setSelectValue] = useState({});
2nd: Create options:
const options = [
{ label: "Tomate", value: 1 },
{ label: "Queso", value: 2 }
];
3rd: add method onChange in select and pass options:
const onChange = (ev) => {
setSelectValue(ev);
//console.log(ev);
console.log(selectValue);
};
<Select
id="dropdown-menu"
placeholder={"e"}
options={options}
onChange={onChange}
/>
I have created demo here:
https://codesandbox.io/s/ancient-pond-96h53?file=/src/App.js

Unable to maintain formik state values within react-select component

I am using react-select (multi-values) with Formik together with Material-UI Stepper (wizard) and have the values stored successfully within Formik's initialStates values but when I advance to the next screen, using conditional component rendering within my Stepper and then return back one screen where my react-select component resides, it no longer holds/shows the values that have already been selected within my react-select component even though the values are still in Formik's initialStates values.
My state is as follows which is storing my selectedOptions correctly:
import Select from 'react-select';
const myOptions= [
{ value: 'Food', label: 'Food' },
{ value: 'Being Fabulous', label: 'Being Fabulous' },
{ value: 'Unicorns', label: 'Unicorns' },
{ value: 'Kittens', label: 'Kittens' },
];
"props": {
"myGroups": [
{
"myGroupName": "",
"selectedOptions": [
{
"value": "Unicorns",
"label": "Unicorns"
},
{
"value": "Kittens",
"label": "Kittens"
}
]
}
]
}
Here is the code for the react-select component:
<Formik initialValues={initialFormValues} validationSchema={formSchema} onSubmit={this.handleFormSubmit} enableReinitialize>
{({ handleSubmit, handleChange }) => (
<Form noValidate onSubmit={handleSubmit} autoComplete='off'>
<Select
options={myOptions}
isMulti={true}
name={`myGroups.${index}.selectedOptions`}
onChange={(selectedOption) => {
let e = { target: { name: `myGroups.${index}.selectedOptions`, value: selectedOption } };
handleChange(e);
}}
/>
</Form>
)}
</Formik>
When returning, I expected to see both "Unicorns" and "Kittens" within the select but it's empty.
Any ideas how I can maintain state within this component? Can I perhaps somehow use `defaultValue' ?
The only solution I found is to install this library : formik-material-ui
You can after easy import:
import { Select } from 'formik-material-ui';
And use it in the Field component props.

How to format value of the React Select

I use "react final forms" and "react select".
I've organized an interface and functionality works, but I have one thing which I don't like.
React select requires options in next format:
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
and the value also should be set as { value: 'chocolate', label: 'Chocolate' }.
But for me is strange to have in my model of data (and send to the server also) the value like this - { value: 'chocolate', label: 'Chocolate' }, because I need only 'chocolate'.
Easy way is format the object into a single value after the form will be saved and format back from single value to the object before the rendering of the element. I know how to do it outside of form, but in this case I should solve this problem again and again for each select element separately.
What I would like to do:
Find a way how to set value of the react select as single value, like 'chocolate' instead of object.
OR
Create a wrapper for react select component and format value there when it sets (it's easy) and when the form get the value from this field (this I don't know how to do).
I will appreciate any help.
Method 1
With strings:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const data = ["1", "2"];
function SingleStringReactSelect() {
const [option, setOption] = useState();
return (
<section>
<Select
onChange={option => setOption(option)}
value={[option]}
getOptionLabel={label => label}
getOptionValue={value => value}
closeMenuOSelect={false}
options={data}
/>
</section>
);
}
Method 2:
Created example:
https://codesandbox.io/s/react-codesandboxer-example-z57ke
You can use map in options and with Wrapper like SingleValueReactSelect
import React, { Fragment, useState } from "react";
import Select from "react-select";
const data = ["chocolate", "strawberry", "vanilla"];
export function SingleValueReactSelect(props) {
const [selectedItem, setSelectedItem] = useState();
return (
<Select
{...props}
value={selectedItem}
onChange={item => {
setSelectedItem(item);
props.onChange(item.value);
}}
options={props.options.map(item => ({ label: item, value: item }))}
/>
);
}
export default function AppDemo() {
return (
<Fragment>
<p>SingleValueReactSelect Demo</p>
<SingleValueReactSelect
isClearable
isSearchable
options={data}
onChange={item => {
alert(item);
}}
/>
</Fragment>
);
}
You can transform/filter the object data once you are sending the form data to server or another component.
Setting the value of the react-select as a single value will not show it as selected in the React-Select.

React Grommet Select - passing in object data as options

I am using Grommet v2 components and trying to mirror the display used in the Select 'Seasons' example in Grommet's storybook.
The field appears like this:
The difference is my data needs to separate label and value:
const Options = [
{
label: "S01",
value: "283736"
},
{
label: "S02",
value: "293774"
},
instead of using the default:
const Options = [
"S01",
"S02",
Here is an example on Codesandbox
The object format was used in Grommet's example of ObjectMultiSelect in their storybook. I found the Select component needs
labelKey="label" and valueKey="value" to load the objects as options, but adding these two props seems to break the component options.
I would like for the data passed in to resemble
const Options = [
{
label: "S01",
value: "283736"
},
{
label: "S02",
value: "293774"
},
and still have the options displayed as above.
<Select
options={[
{
lab: "S01",
val: "283736"
},
{
lab: "S02",
value: "293774"
}
]}
labelKey="lab"
dropHeight="large"
name="primaryServer"
value={this.props.values.primaryServer}
placeholder="Select Primary Server"
emptySearchMessage={"No Servers Available"}
onChange={({ option }) => {
console.log(option.lab)
console.log(option.val)
}}
/>
// Output // S01 // 283736
// This worked for me. labelKey="lab" is required.

Resources