I'm trying to create a custom Select using react-select. I want to create a custom Option and SingleValue. The custom Option renders, but SingleValue doesn't. The single value (selected value) displays as per the default styling.
Here's my code,
const Option = (props) => {
const { data, innerProps, innerRef, cx, getStyles, className } = props;
const [hover, setHover] = useState(false);
return (
<div ref={innerRef} {...innerProps}
style={getStyles('option', props)}
className={cx(
{
option: true,
},
className
)}
>
<div style={{ marginLeft: 10}} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}>
<p> {data.label} </p>
{ hover ? <Alert bsStyle="success" style={{ opacity: 0.8 }}>
<p> {data.description} </p>
</Alert> : null}
</div>
</div>
)
};
I have tried SingleValue like this,
const SingleValue = ({
cx,
getStyles,
selectProps,
data,
isDisabled,
className,
...props
}) => {
console.log(props);
return (
<div
className={cx(
emotionCss(getStyles("singleValue", props)),
{
"single-value": true,
"single-value--is-disabled": isDisabled
},
className
)}
>
<div>{data.label}</div>
<div style={{ fontSize: "10px" }}>{data.description}</div>
</div>
);
};
And this,
const SingleValue = props => (
<components.SingleValue {...props}>
{props.data.description}
</components.SingleValue>
);
I render it like this,
<Select
id="color"
options={this.props.options}
isMulti={true}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={this.props.value}
components={{ Option, SingleValue }}
/>
Both ways of SingleValue don't work. I have tried to just include SingleValue in components, but that also does't work. Could anyone please help? Thanks!
I had the same problem and I solved it using components.ValueContainer instead of components.SingleValue. In my case, I used react-select to have an icon select component.
This is my constructor:
constructor(props) {
super(props);
this.state = {
icons: [{label: "temperature-low", value: "temperature-low", icon: "temperature-low"}, {label: "utensils", value: "utensils", icon: "utensils"}, {label: "venus", value: "venus", icon: "venus"}, {label: "volume-up", value: "volume-up", icon: "volume-up"}, {label: "wifi", value: "wifi", icon: "wifi"}],
inputValueIcon: "",
};
this.handleChangeIcon = this.handleChangeIcon.bind(this);
}
This is my handleChange function:
handleChangeIcon(selectedItem) {
this.setState({
inputValueIcon: selectedItem
})
}
And finally, my render function:
render() {
const { Option, ValueOption, ValueContainer } = components;
const IconOption = (props) => (
<Option {...props}>
<MDBIcon icon={props.data.label} />
</Option>
);
const ValueOptionLabel = (props) => (
<ValueContainer {...props}>
<MDBIcon icon={props.data.label} />
</ValueContainer>
);
return(
<Fragment>
<Select
placeholder="Select an icon"
value={this.state.inputValueIcon}
onChange={ this.handleChangeIcon }
components={{ Option: IconOption, SingleValue: ValueOptionLabel }}
options={ this.state.icons }
/>
</Fragment>
);
}
It's not the best solution, but it works :)
Related
I have the following component:
const WorkProviderList: React.FC<StepComponentParams> = ({
metadata,
}): JSX.Element => {
const {
control,
setValue,
getValues,
} = useForm({
resolver: yupResolver(validationSchema),
defaultValues: { [CONFIRM_NAME]: true } as any,
mode: "all",
});
...
const isOtherFieldNameVisible = getValues()[FIELD_NAME] === "other";
console.log("isOtherFieldNameVisible: ", isOtherFieldNameVisible);
return (
<>
<Text element="h1" variant="title2Bold">
Which is your work provider?
</Text>
<Text>
If you work with more than one, select the provider that you work with
the most.
</Text>
<fieldset role="group" aria-labelledby={FIELD_NAME}>
<Controller
name={FIELD_NAME}
control={control}
render={() => (
<RadioGroupFields
fields={radioFields}
name={FIELD_NAME}
onChange={(e) => {
setValue(FIELD_NAME, e.target.value, {
shouldValidate: true,
});
}}
/>
)}
/>
</fieldset>
{isOtherFieldNameVisible && (
<div className="py-3">
<FormField
name={OTHER_FIELD_NAME}
label="What is the name of your work provider?"
>
<Controller
name={OTHER_FIELD_NAME}
control={control}
render={({ field: { onChange } }) => (
<TextField
id="other_input"
isFullWidth
name={OTHER_FIELD_NAME}
statusType="info"
onChange={onChange}
/>
)}
/>
</FormField>
</div>
)}
</>
);
};
and the following test:
const { getByLabelText, getByText } = render(
<WorkProviderList {...props} />
);
const input = getByLabelText("I work with a different work provider");
fireEvent.change(input, { target: { value: "other" } });
fireEvent.click(input);
const text = getByText("What is the name of your work provider?");
expect(text).toBeInTheDocument();
even though the console.log in the component returns true, I would expect to find the label I am looking for but I get the following error:
TestingLibraryElementError: Unable to find a label with the text of:
What is the name of your work provider?
I'm implementing a component Autocomplete using Material UI library.
But there's a problem - I'm not sure how to pass value and onChange properly, because I have a custom implementation of TextField that requires value and onChange as well. Should I pass value and onChange twice - to Autocomplete and TextField? Or maybe there's a better solution? Would appreciate any help!
Here's my code:
import { Autocomplete as MuiAutocomplete } from '#material-ui/lab'
import { FormControl } from 'components/_helpers/FormControl'
import { useStyles } from 'components/Select/styles'
import { Props as TextFieldProps, TextField } from 'components/TextField'
export type Props = Omit<TextFieldProps, 'children'> & {
options: Array<any>
value: string
onChange: (value: string) => void
disabled?: boolean
}
export const Autocomplete = (props: Props) => {
const classes = useStyles()
return (
<FormControl
label={props.label}
error={props.error}
helperText={props.helperText}
>
<MuiAutocomplete
options={props.options}
// value={props.value}
// onChange={event =>
// props.onChange((event.target as HTMLInputElement).value as string)
// }
classes={{
option: classes.menuItem,
}}
disabled={props.disabled}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange}
/>
)}
renderOption={option => {
return <Typography>{option.label}</Typography>
}}
/>
</FormControl>
)
}```
Material UI has props built in to handle the state of the Autocomplete vs input values.
You can see it in use in the docs here: https://material-ui.com/components/autocomplete/#controllable-states
In your example, you would want to add the inputChange and onInputChange props to the Autocomplete component. These will get passed down to your TextField through the params passed to the renderInput function.
So your final code would look something like the below snippet copied from the linked documentation:
<Autocomplete
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" variant="outlined" />}
/>
import React, { useEffect, useState } from "react";
import { Autocomplete } from "#mui/material/node";
import { Controller, useFormContext } from "react-hook-form";
import { TextField } from "#mui/material";
import PropTypes from "prop-types";
const valueFunc = (arr, id) => {
const temp = arr.length > 0 && arr?.find((element) => element.id === id);
return temp;
};
AutocompleteSearch.propTypes = {
options: PropTypes.arrayOf({
title: PropTypes.string,
id: PropTypes.string,
}),
name: PropTypes.string,
};
export default function AutocompleteSearch({
name,
options,
label,
id,
...other
}) {
const [temp, setTemp] = useState({});
const { control, setValue } = useFormContext();
useEffect(async () => {
const found = valueFunc(options, id);
await setTemp(found);
}, [options, id]);
return (
<Controller
control={control}
name={name}
rules={{ required: true }}
render={({ fieldState: { error } }) => (
<>
<div >
<Autocomplete
id="controllable-states-demo"
onChange={(_, v) => {
setValue(name, v?.id);
setTemp(v);
}}
onBlur={(e) => {
e.target.value == "" && setValue(name, "");
}}
value={temp}
options={options}
getOptionLabel={(item) => (item.title ? item.title : "")}
renderInput={(params) => (
<>
<TextField
{...params}
label={label}
InputLabelProps={{
style: {
fontSize: "14px",
fontWeight: "400",
color: "#FF5B00",
},
}}
size="small"
error={temp === null && !!error}
helperText={temp === null && error?.message}
{...other}
/>
</>
)}
/>
</div>
</>
)}
/>
);
}
<AutocompleteSearch
name="pharmacy_group_title"
label="Pharmacy Group"
options={pharmacyGroups} // Array {id , title}
id={defaultValues?.pharmacy_group_title} // ID
/>
const [application, setApplication] = useState([])
const [app, setApp] = useState([
{
id: null,
code: null,
name: null
}
]);
useEffect(() => {
let ignore = false;
(async function load() {
let response = await getAllData();
if (!ignore) setApplication(response['data'])
})()
return () => ignore = true;
},[]);
{
label: (
<div className="flex items-center">
<label className="flex-1">Application</label>
<div className="text-right">
<ButtonGroup>
<IconButton icon={<Icon icon="plus" />} onClick={() => appendApp()} />
<IconButton onClick={() => removeApp()} size="md" icon={<Icon icon="minus" />} style={{ display: app.length > 1 ? 'inline-block' : 'none' }} />
</ButtonGroup>
</div>
</div>
),
name: 'applications',
renderer: (data) => {
const { control, register, errors } = useFormContext();
return (
<div className="flex flex-col w-full">
{
app.map((item, index) => (
<div key={index} className="flex flex-col pb-2 -items-center">
<div className="flex pb-2 w-full">
<SelectPicker
placeholder="Select Application"
data={application['data']}
labelKey="name"
valueKey="code"
style={{ width: '100%' }}
disabledItemValues={Array.isArray(control.getValues()['applications']) ? control.getValues()['applications'].map(x => x.id) : []}
onChange={(value) => control.setValue('applications', _setApp(value, index, 'code'))}
value={control.getValues()['applications']?.code}
/>
</div>
</div>
))
}
</div>
)
}
const appendApp = () => {
let i = 0;
for (i = 0; i < noOfApp; i++) {
setApp(arr => [...arr, {
id: null,
code: null,
name: null,
role: null
}]);
return app;
}
}
const removeAppRole = () => {
setApp([...app.slice(0, -1)]);
}
const _setApp = (value, idx, status) => {
app[idx][status] = value;
setApp(app);
return app;
}
How do I add a validation on the select? for example when the select field is empty it should validation that it is required to select. also for example when there's a existing data which is like this:
data = [{
id: 1,
name: 'IOS',
code: 'ios'
}]
and how do I display this data on the select field? cause I have a create and edit.
when I try to edit it doesn't display the value.
I do not use the register, I prefer to use Controller, for these cases it is more practical, in the v7 of react-hook-form, see this example:
My select component:
import { ErrorMessage } from "#hookform/error-message";
import { IonItem, IonLabel, IonSelect, IonSelectOption } from "#ionic/react";
import { FunctionComponent } from "react";
import { Controller } from "react-hook-form";
import React from "react";
const Select: FunctionComponent<Props> = ({
options,
control,
errors,
defaultValue,
name,
label,
rules
}) => {
return (
<>
<IonItem className="mb-4">
<IonLabel position="floating" color="primary">
{label}
</IonLabel>
<Controller
render={({ field: { onChange, onBlur, value } }) => (
<IonSelect
value={value}
onIonChange={onChange}
onIonBlur={onBlur}
interface="action-sheet"
className="mt-2"
>
{options ? options.map((opcion) => {
return (
<IonSelectOption value={opcion.value} key={opcion.value}>
{opcion.label}
</IonSelectOption>
);
}):""}
</IonSelect>
)}
control={control}
name={name}
defaultValue={defaultValue}
rules={rules}
/>
</IonItem>
<ErrorMessage
errors={errors}
name={name}
as={<div className="text-red-600 px-6" />}
/>
</>
);
};
export default Select;
Use the component in other component:
import Select from "components/Select/Select";
import { useForm } from "react-hook-form";
import Scaffold from "components/Scaffold/Scaffold";
import React from "react";
let defaultValues = {
subjectId: "1"
};
const options = [
{
label: "Option1",
value: "1",
},
{
label: "Option2",
value: "2",
},
];
const ContactUs: React.FC = () => {
const {
control,
handleSubmit,
formState: { isSubmitting, isValid, errors },
} = useForm({
defaultValues: defaultValues,
mode: "onChange",
});
const handlerSendButton = async (select) => {
console.log(select);
};
const rulesSubject = {
required: "this field is required",
};
return (
<Scaffold>
<Scaffold.Content>
<h6 className="text-2xl font-bold text-center">
Contact us
</h6>
<Select
control={control}
errors={errors}
defaultValue={defaultValues.subjectId}
options={options}
name="subjectId"
label={"Subject"}
rules={rulesSubject}
/>
</Scaffold.Content>
<Scaffold.Footer>
<Button
onClick={handleSubmit(handlerSendButton)}
disabled={!isValid || isSubmitting}
>
Save
</Button>
</Scaffold.Footer>
</Scaffold>
);
};
In this case i use Ionic for the UI but you can use MaterialUI, ReactSuite o other framework, its the same.
I hope it helps you, good luck.
EDIT
A repository : Ionic React Select Form Hook
A codeSandBox: Ionic React Select Form Hook
i'm trying to create a custom color picker with formik form
the probleme her is that parent component color are not changed :
import {SketchPicker} from "react-color";
export const MyColorPicker = ({label, ...props}) => {
// with useField is should not use onChange but i get an error without defining it myself
const [field] = useField(props);
const [color, setColor] = useState("#333");
const handleChange = color => {
setColor(color.hex);
field.onChange(color.hex);
};
const onComplete = color => {
setColor(color.hex);
field.onChange(color.hex);
};
return (
<div style={{padding: 10}}>
<label>{label}</label>
<SketchPicker {...props} {...field} color={color} onChange={handleChange} onChangeComplete={onComplete} />
</div>
);
};
as exemple this work :
export const MyTextAreaField = ({label, ...props}) => {
const [field, meta] = useField(props);
if (field && field.value === null) {
field.value = "";
}
return (
<div style={{display: "flex", flexDirection: "column"}}>
<label className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated MuiInputLabel-shrink MuiFormLabel-filled">
{label}
</label>
<TextareaAutosize
rows={10}
{...field}
{...props}
style={{marginTop: 10, fontFamily: "Helvetica Neue", fontSize: 15}}
/>
{meta.touched && meta.error ? <div className="error">{meta.error}</div> : null}
</div>
);
};
and parent code :
<Formik
initialValues={{
data: { title :'', shortDescription:'', description:'', color:'')
}}
onSubmit={values => {
console.log(values.data) ; // data.color stay null
}}>
<Form>
<MyTextAreaField id="data.description" name="data.description" label={t("PROJECT.DESCRIPTION")} />
<MyColorPicker id="data.color" label={t("PROJET.COLOR")} name="data.color" />
</Form>
</Formik>
finally i ended doing something like this :
In parent Component :
<MyColorPicker
label={t("PROJECT.COLOR")}
onChange={color => {
data.project.color = color;
}}
/>
Component definition
export const MyColorPicker = ({label, onChange}) => {
const [color, setColor] = useState("#333");
const handleChange = color => {
setColor(color.hex);
};
return (
<div
style={{display: "flex", flexDirection: "row", justifyContent: "flex-start", alignItems: "center", padding: 10}}>
<label>{label}</label>
<ChromePicker color={color} onChange={handleChange} onChangeComplete={color=> onChange(color.hex) } />
</div>
)
})
Im fairly new to React and i'm trying to create a dropdown where users can add values to the dropdown. Something like this What i want
This is what i got now, but the add button dosent work at all
My dropdown
I had another input field where i could pass the value to the dropdown, but when i tried to implement the logic to the downshift dropdown nothing happened. No error, no value!
Here is my code:
function BasicAutocomplete({ items, onChange }) {
return (
<Downshift
onChange={onChange}
render={({
getInputProps,
getItemProps,
isOpen,
inputValue,
selectedItem,
highlightedIndex,
handleSubmit
}) => (
<div>
<Input {...getInputProps({ placeholder: 'Markedsaktivitet'}) } ref="input" />
{isOpen ? (
<div style={{ border: '1px solid #ccc' }}>
{items
.filter(
i =>
!inputValue ||
i.toLowerCase().includes(inputValue.toLowerCase()),
)
.map((item, index) => (
<div
{...getItemProps({ item }) }
key={item}
style={{
backgroundColor:
highlightedIndex === index ? 'gray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
}}
>
{ item }
</div>
))}
<Button type="button" onClick={handleSubmit}><i className="fa fa-plus" /> Add option</Button>
</div>
) : null}
</div>
)}
/>
)
}
class Dropdown extends React.Component {
constructor(props) {
super(props)
this.state = {
inputField: 'no value',
items: ['apple', 'orange', 'carrot']
}
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit() {
const newItem = this.refs.input.value
this.setState({ items: this.state.items.concat(newItem) })
}
render() {
return (
<Wrapper>
<BasicAutocomplete
items={this.state.items}
onChange={selectedItem => console.log(selectedItem)}
onClick={this.handleSubmit}
/>
</Wrapper>
);
}
}
Thanks for the replays!
use bootstrap dropdown menu its good and nice looking
check out
and maybe you find something
Check here there is diffrent model