i'm entirely lost when it comes to figuring this out. I have a existing select that works with formik & react-select, but I can't get it to work as a multi. Here's what i have so far:
import Select from 'react-select';
import { useField } from 'formik';
export default function SelectField(props) {
const [field, state, { setValue, setTouched }] = useField(props.field.name);
const onChange = ({ value }) => {
setValue(value);
};
return <Select {...props} onChange={onChange} onBlur={setTouched} />;
}
and
<Field
component={SelectField}
name="campfeatures"
options={selectObjects}
/>
How would I turn this into being capable of taking multi select? If I add isMulti to the Field, it "works" but it doesn't actually retain the multiple values.
Thanks for your help!
The argument type for onChange changes when the react-select receives isMulti from a single object to a list of objects. When using isMulti you don't need to destruct; the first parameter is the value.
You also want to make the react-select a controlled component by managing its value.
export default function SelectField(props) {
const [field, state, { setValue, setTouched }] = useField(props.field.name);
// value is an array now
const onChange = (value) => {
setValue(value);
};
// use value to make this a controlled component
// now when the form receives a value for 'campfeatures' it will populate as expected
return <Select {...props} value={state?.value} isMulti onChange={onChange} onBlur={setTouched} />;
}
The new value is an array of the selected options with label and value fields. If you want to store just the value you'll need to map it to a value and modify the react-select to handle that
Related
I am using Chakra UI with React Typescript and implementing a checkbox group
The default values are controlled by an outside state that is passed down as a prop.
The problem is that the CheckboxGroup doesn't accept the default values from outside source
The code is as follows:
import React, {FC, useCallback, useEffect, useState} from "react";
import { CheckboxGroup, Checkbox, VStack } from "#chakra-ui/react";
interface IGroupCheckbox {
values: StringOrNumber[],
labels: StringOrNumber[],
activeValues: StringOrNumber[]
onChange: (value:StringOrNumber[])=> void
}
const GroupCheckbox:FC<IGroupCheckbox> = ({
values,
labels,
activeValues,
onChange
}) => {
const [currActiveValues, setCurrActiveValues] = useState<StringOrNumber[]>();
const handleChange = useCallback((value:StringOrNumber[]) => {
if(value?.length === 0) {
alert('you must have at least one supported language');
return;
}
onChange(value);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(()=>{
if(activeValues) {
setCurrActiveValues(['en'])
}
},[activeValues])
return (
<CheckboxGroup
onChange={handleChange}
defaultValue={currActiveValues}
>
<VStack>
{values && labels && values.map((item:StringOrNumber, index:number)=>
{
return (
<Checkbox
key={item}
value={item}
>
{labels[index]}
</Checkbox>
)
}
)}
</VStack>
</CheckboxGroup>
)
}
export default GroupCheckbox
When I change the defaultValue parameter, instead of the state managed, to be defaultValue={['en']} it works fine, but any other input for this prop doesn't work.
I checked and triple checked that the values are correct.
Generally, passing a defaultValue prop "from an outside source" actually does work. I guess that in your code, only ['en'] works correctly because you explicitly use setCurrActiveValues(['en']) instead of setCurrActiveValues(activeValues).
The defaultValue prop will only be considered on the initial render of the component; changing the defaultValue prop afterwards will be ignored. Solution: make it a controlled component, by using the value prop instead of defaultValue.
Note that unless you pass a parameter to useState(), you will default the state variable to undefined on that initial render.
Side note: You also don't need a separate state variable currActiveValues. Instead, you can simply use activeValues directly.
const GroupCheckbox = ({ values, labels, activeValues, onChange }: IGroupCheckbox) => {
const handleChange = useCallback(
(value: StringOrNumber[]) => {
if (value?.length === 0) {
alert("you must have at least one supported language")
return
}
onChange(value)
},
[onChange]
)
return (
<CheckboxGroup onChange={handleChange} value={activeValues}>
<VStack>
{labels && values?.map((item: StringOrNumber, index: number) => (
<Checkbox key={item} value={item}>
{labels[index]}
</Checkbox>
))}
</VStack>
</CheckboxGroup>
)
}
if I console.log(data.value) it indeed shows array of chosen options. But when I console.log in validation function in form or submit the form, then the value of that field is always boolean true
initial value is [], but regardless of which multiple options I choose, the output is always boolean true. Diagnoses are strings. StateOptions is array of objects with key, value and text
with the rest of code always all fields(I do not write them here) give value, which was written to them
Looks like setFieldValue function(one of default Formik props) works wrong
this is the code of the field, which always has value true:
import { Dropdown, DropdownProps, Form } from "semantic-ui-react";
<DiagnosisSelection
diagnoses={diagnoses}
name="diagnosisCodes"
setFieldValue={setFieldTouched}
setFieldTouched={setFieldValue}
disabled={!(entrytype)}
/>
export const DiagnosisSelection = ({
diagnoses,
setFieldValue,
setFieldTouched,
disabled,
name
}: {
diagnoses: DiagnosisEntry[];
setFieldValue: FormikProps<{ diagnosisCodes: string[] }>["setFieldValue"];
setFieldTouched: FormikProps<{ diagnosisCodes: string[] }>["setFieldTouched"];
disabled:boolean;
name:string;
}) => {
const field = "diagnosisCodes";
const onChange = (
_event: React.SyntheticEvent<HTMLElement, Event>,
data: DropdownProps
) => {
setFieldTouched(field, true);
setFieldValue(field, data.value);
};
const stateOptions = diagnoses.map(diagnosis => ({
key: diagnosis.code,
text: `${diagnosis.name} (${diagnosis.code})`,
value: diagnosis.code
}));
if (disabled)
return null
return (
<Form.Field name={name}>
<label>Diagnoses</label>
<Dropdown
name={name}
fluid
multiple
search
selection
options={stateOptions}
onChange={onChange}
/>
<ErrorMessage name={field} />
</Form.Field>
);
};
It appeared that I wrote wrong: setFieldValue was setFieldTouched and viceversa:
<DiagnosisSelection
diagnoses={diagnoses}
name="diagnosisCodes"
setFieldValue={setFieldValue}
setFieldTouched={setFieldTouched}
disabled={!(entrytype)}
/>
I am unable to figure out how I could get pre-populated value from disabled input field. Since the field is disabled onChange won't apply here. Even with inputRef property of TextField, I am able to trigger a function called getNameInputValue and can see the values via console. But when I try to set the state to a function coming via props, I get null errors for value.
Here's the relevant code snippet:
const getNameInputValue = (id, nameValue) => {
console.log(id, nameValue.value); //works
props.getNameValues({ ...nameValues, [id]: nameValue.value }) //doesnt work
}
return (
<Box>
<Typography>NAME</Typography>
<TextField
id={`name-${index}`}
disabled
defaultValue={nameValueComingFromTheLoop}
variant="outlined"
inputRef={(inputValue) => getNameInputValue(`name-${index}`,inputValue)}
/>
</Box>
);
As requested getNameValues is defined in an outer component, here is its definition:
const [nameValue, setNameValue] = useState({});
const getNameValues= (receivedNameValuesObj) => {
setNameValue(receivedNameValuesObj);
};
I am passing through the react-select Select component as an InputComponent within the Material-UI InputBase component. I have successfully been able to populate the value from the options, however, I'm unable to use isClearable.
When isClearable is triggered, null is passed to the handleChange(event) function and I'm hoping there is a way to force an object through to prevent null creating an error.
The handleChange function within InputBase has var element = event.target || inputRef.current. As event is null, it's not even getting to inputRef which will contain the required object.
Would be good to get this working as an uncontrolled component.
I have created a codebox to illustrate the problem: https://codesandbox.io/s/morning-feather-l7xqf
You could supply your custom onChange() to catch the null and pass through your own value:
// Deconstruct from otherProps
const SelectWrapper = ({ inputRef, onChange, ...otherProps }) => {
function handleChange(event) {
// Overwrite the event with your own object if it doesn't exist
if (!event) {
event = {
target: inputRef,
value: '',
};
}
onChange(event);
}
return (
// Pass in the custom handleChange
<Select styles={customStyle} isClearable ref={inputRef} onChange={handleChange} {...otherProps} />
);
};
In our rather complex form we have a dynamic form field (similar to the example in the antd documentation, except using a Select field). We use initialValue to feed our Form data from the database, now we want to have our Select fields, which are added dynamically, to have a default value.
The problem exists in the fact that it isn't possible to add an initialValue to fields that haven't rendered yet + form doesn't know how many dynamic Select fields will be added.
So instinctively I resorted to the defaultValue prop on the Select box, which in my eyes should just work, but it doesn't. (in antd 4 there is no fieldDescriptor with a defaultValue)
Maybe this example will explain better what I'm trying to say:
https://codesandbox.io/s/thirsty-hamilton-m7bmc
If you add a field in the example and hit submit, it will complain the field is required. However it certainly does have a value in there, but apparently not for the Form state.
I hope someone else has encountered a similar issue
Your Select components need VALUE to be defined. I see you have defaultValue, but they also need such properties as VALUE, and ONCHANGE. When you add them the select will start to work properly.
1). It is better to define default value inside VALUE property, so that if nothing is selected by user (selected value is undefined), the value will default to whatever you mention in the following condition with ternary operator:
value={selectedValue ? selectedValue : defaultValue}.
2). In onChange property you need to pass value and update the state with that value:
onChange={(value) => this.updateSelectedValue(value)}
import React, { PureComponent } from "react";
import { Form, Select } from "antd";
const { Option } = Select;
export default class DynamicFieldSet extends PureComponent {
state = {
selectedValue: undefined,
}
updateSelectedValue = value => this.setState({ selectedValue: value })
render() {
const { selectedValue } = this.state;
return (
<Form>
<Form.Item>
<Select
value={selectedValue ? selectedValue : "TAG"}
onChange={(value) => this.updateSelectedValue(value)}
>
<Option value="MARKER">Marker</Option>
<Option value="TAG">Tag</Option>
<Option value="FIELD">Field</Option>
</Select>
</Form.Item>
</Form>
);
}
}