Storybook custom onChange - reactjs

I'm having trouble updating knobs when adding custom onchange
export default {
component: Input,
decorators: [
(Story) => <div style={{ textAlign: 'center', width: '70%' }}><Story /></div>,
],
title: 'components/Input',
} as ComponentMeta<typeof Input>;
const Template: ComponentStory<typeof Input> = (args) => {
const [value, setValue] = React.useState(args.value);
return <Input
{...args}
onChange={e => { setValue(e.target.value); args.onChange(e); }}
onClear={() => { setValue(''); args.onClear(); }}
value={value}
/>;
};
After typing a value in knobs onchange the input value does not update and vice versa after typing something in input the value in knobs does not update

Related

Storing State in React JS not working as excepted

what i am creating here is a sorter with 3 inputs like shown here . This sorter will get some data from a table. Now i'm setting state with initSelect and i'm passing it the fields array but when i console.log(select) it gives me the object shown in the image which is incorrect from the behaviours i want {sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
The first input has to have a default value of 'Date' always , but it can change to other values in the dropdown list like name , username ect . When i console log the select state it is messed up as it's always selecting the last one on the fields array , how can i change the initSelects function to correctly build the object i want.
Also the tricky thing which i can't seem to do is , if this Date value is selcted , in the second input, the date value should not be there. And if in the second input we select another value like Earth , Earth and Date should not be in the 3rd input and so on. So basically it means filtering out values . I need serious help as this is for the company i work on
Excepted Behaviour: Dynamically update value every time i select one input element like
{sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
When selectin 'Date' for example , it shouldn't not be included in the dropdownlist on sorterParam2, sorterParam3.
/*eslint-disable*/
import React, { useState, useMemo } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
import { GridSortModel } from '#mui/x-data-grid';
import SorterField from './SorterField';
const initSelects = (fields) => {
let object = {};
fields.map((item) => {
console.log(item, 'item');
object = {
...item,
[item.name]: item.defaultValue ? item.defaultValue : '',
};
});
return object;
};
const Sorter = ({ menuItemsValue, setSortData }: SortProps) => {
const fields: SorterProps[] = [
{
name: 'sorterParam1',
title: 'Sort by',
optional: false,
defaultValue: 'Date',
},
{
name: 'sorterParam2',
title: 'Then by',
optional: true,
},
{
name: 'sorterParam3',
title: 'Then by',
optional: true,
},
];
const [select, setSelect] = useState<any>(() => initSelects(fields));
const getMenuItems = useMemo(() => {
return menuItemsValue.filter((item) => select.sorterParam1 !== item);
}, [menuItemsValue, select]);
const handleSelectChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
console.log(111, name, value);
setSelect({ ...select, [name]: value });
setSortData(sortOptions);
};
const handleClearAllInputs = () => {
setSelect({
sorterParam1: '',
sorterParam2: '',
sorterParam3: '',
});
};
const handleConfirm = () => {};
return (
<TextField
label="Advanced Sorting"
className={styles.sorter__inputs}
id="sorter-parameter-1"
variant="standard"
InputProps={{
disableUnderline: true,
}}
select
SelectProps={{
IconComponent: (props) => <NewIcon {...props} />,
}}
sx={{
fontSize: '12px',
width: '100%',
'& .MuiInputBase-input:focus': {
backgroundColor: 'transparent !important',
},
'& .MuiInputLabel-root': {
color: '#9E9E9E',
},
'& .MuiTextField-root': {
fontSize: '13px',
},
'& .MuiOutlinedInput-root': {
backgroundColor: '#fff',
},
}}
>
{fields.map((option, index) => (
<SorterField
key={option.name}
menuItemsValue={getMenuItems}
name={option.name}
option={option}
count={fields.length}
handleChange={handleSelectChange}
index={index + 1} // setData={setData}
/>
))}
<div className={styles.sorter__inputControllers}>
<Button
className={styles.sorter__clearAllInput}
onClick={() => handleClearAllInputs()}
>
Clear All
</Button>
<Button
onClick={() => handleConfirm()}
className={styles.sorter__confirmInput}
>
Confirm
</Button>
</div>
</TextField>
);
};
export default Sorter;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is the SorterField Component code if that might be helpful
/*eslint-disable*/
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import { MenuItem } from '#mui/material';
import { SorterProps } from '../../types/Sorter';
import { ReactComponent as SorterLine } from '../../assets/img/sortLine.svg';
import styles from '../../assets/components/Sorter/sorter.module.scss';
type SorterFieldProps = {
menuItemsValue: string[];
option: SorterProps;
count: number;
name: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
index: number;
};
function SorterField({
option,
count,
menuItemsValue,
handleChange,
index,
}: SorterFieldProps) {
const handleSorting = () => {};
return (
<div className={styles.sorter__container}>
<div className={styles.sorter__header}>
<p className={styles.sorter__label}>
{option.title}{' '}
{option.optional && (
<sup className={styles.sorter__optional}>*Optional</sup>
)}
</p>
<div className={styles.sorter__numbers__container}>
{Array.from({ length: count }, (_, i) => i + 1).map((number) => (
<>
{number === index ? (
<>
<span className={styles.sorter__number}>{index}</span>
</>
) : (
<>
<span className={styles.sorter__numbers}>{number}</span>
</>
)}
</>
))}
</div>
</div>
<div className={styles.sorter__inputs}>
<TextField
className={[styles.sorter__input, styles.sorter__field__input].join(
' '
)}
variant="outlined"
label="Select"
select
SelectProps={{
IconComponent: () => <NewIcon />,
}}
value={option.defaultValue}
onChange={handleChange}
name={option.title}
size="small"
>
{menuItemsValue.map((title, idx) => (
<MenuItem key={idx} value={title}>
{title}
</MenuItem>
))}
</TextField>
<div onClick={handleSorting} className={styles.sorter__sortOrder}>
<div className={styles.sorter__sortOrder__alphabetical}>
<span>A</span>
<span>Z</span>
</div>
<div className={styles.sorter__sortOrder__sortLine}>
<SorterLine />
</div>
</div>
</div>
</div>
);
}
export default SorterField;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You're re-assigning a value to object in each iteration of fields.map().
If you want to build object as a map of field item name to defaultValue (or blank string), use this instead...
return Object.fromEntries(
fields.map(({ name, defaultValue }) => [name, defaultValue ?? ""])
);
See Object.fromEntries()
Also, move the fields declaration outside your component. It's static content so can be omitted from being declared every render and from any hook dependencies.
You could also use fields.reduce() which is basically the same thing
return fields.reduce(
(obj, { name, defaultValue }) => ({
...obj,
[name]: defaultValue ?? "",
}),
{}
);
As for removing selected options as you iterate through the fields, that's a little trickier.
You could use a memo hook to create an iterable data structure that includes the available options for that particular iteration.
For example
const fieldsWithOptions = useMemo(() => {
const taken = new Set(Object.values(select));
const available = menuItemsValue.filter((item) => !taken.has(item));
return fields.map((field) => ({
...field,
options: select[field.name]
? [select[field.name], ...available] // include the current selection
: available,
}));
}, [menuItemsValue, select]);
Then map over fieldsWithOptions instead of fields and use option.options instead of getMenuItems.

state in custom hook not updating

I am not able to get an updated state from the custom hook.
the example is simple. this is a custom hook allowing to switch from dark to light theme
inside the custom hook, I use useMemo to return a memoized value, but this does not seem to update each time I update the state with switchOn.
I do not wish to use a context in this example
import { useState, useMemo, useCallback } from "react";
const useTheme = (initial) => {
const [isOn, turnOn] = useState(false);
const switchOn = useCallback((e) => turnOn(e.target.checked), [turnOn]);
return useMemo(() => {
return {
theme: isOn ? "light" : "dark",
buttonTheme: isOn ? "dark" : "light",
lightsIsOn: isOn ? "On" : "Off",
isChecked: isOn,
switchOn,
};
}, [switchOn, isOn]);
};
export default useTheme;
import { useState, useRef, useReducer } from "react";
import useTheme from "./useTheme";
import todos from "./reducer";
import "./App.css";
const Item = ({ id, text, done, edit, remove }) => {
const { buttonTheme } = useTheme();
const isDone = done ? "done" : "";
return (
<div style={{ display: "flex" }}>
<li className={isDone} key={id} onClick={() => edit(id)}>
{text}
</li>
<button type="button" onClick={() => remove(id)} className={buttonTheme}>
<small>x</small>
</button>
</div>
);
};
const SwitchInput = () => {
const { switchOn, isChecked, lightsIsOn } = useTheme();
return (
<>
<label class="switch">
<input type="checkbox" onChange={switchOn} checked={isChecked} />
<span class="slider round"></span>
</label>
<span>
{" "}
Lights <b>{lightsIsOn}</b>
</span>
<br /> <br />
</>
);
};
const initialState = {
items: [],
};
const init = (state) => {
return {
...state,
items: [
{ id: 1, text: "Learning the hooks", done: false },
{ id: 2, text: "Study JS", done: false },
{ id: 3, text: "Buy conference ticket", done: false },
],
};
};
function App() {
const inputRef = useRef();
const [state, dispatch] = useReducer(todos, initialState, init);
const [inputValue, setInputValue] = useState(null);
const { theme } = useTheme();
const handleOnChange = (e) => setInputValue(e.target.value);
const handleOnSubmit = (e) => {
e.preventDefault();
dispatch({ type: "add", payload: inputValue });
inputRef.current.value = null;
};
return (
<div className={`App ${theme}`}>
<SwitchInput />
<form onSubmit={handleOnSubmit}>
<input ref={inputRef} type="text" onChange={handleOnChange} />
<ul>
{state.items.map((item) => (
<Item
{...item}
key={item.id}
remove={(id) => dispatch({ type: "remove", payload: { id } })}
edit={(id) => dispatch({ type: "edit", payload: { id } })}
/>
))}
</ul>
</form>
</div>
);
}
export default App;
my custom hook: useTheme.js
below, the component where I want to access logic from custom hook: App.js
I use an App.css to apply theme style : dark and light
below a demo. We can see that the values do not change
How is an effective way to subscribe to state changes share global states between components?
You need to pass the values of useTheme() from <App> to <SwitchInput> to share it as follows.
function App() {
const { theme, switchOn, isChecked, lightsIsOn } = useTheme();
return (
<div className={`App ${theme}`}>
<SwitchInput
switchOn={switchOn}
isChecked={isChecked}
lightsIsOn={lightsIsOn}
/>
</div>
);
}
Then, <SwitchInput> will receive them in props.
const SwitchInput = ({ switchOn, isChecked, lightsIsOn }) => {
return (
<>
<label class="switch">
<input type="checkbox" onChange={switchOn} checked={isChecked} />
<span class="slider round"></span>
</label>
<span>
{" "}
Lights <b>{lightsIsOn}</b>
</span>
<br /> <br />
</>
);
};
In your example, useTheme() is called in both <App> and <SwitchInput>, but theme and other values are initialized separately and are not shared between each component.

Why am I getting a TypeError choices.map is not a function?

I am trying to map the items (choices) in an array to a <select> field with <MenuItem>, I am very close but I am getting a TypeError choices.map is not a function error.
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
formControl: {
margin: theme.spacing(1),
minWidth: 250,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
Below is the section in which the issue is, above is just styling.
function SimpleSelect() {
const classes = useStyles();
const [values, setValues] = React.useState({
firm: '',
infosys: '',
spot: '',
});
const [choices, setChoices] = React.useState([])
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState();
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://127.0.0.1:5000/form/'
);
setChoices({ choices: result.data })
};
fetchData();
}, []);
function handleChange(event) {
setValues(values)
}
return (
<form className={classes.root} autoComplete="off">
<FormControl className={classes.formControl}>
<InputLabel htmlFor="firm-helper">Firm</InputLabel>
<Select
value={values.firm}
onChange={handleChange}
input={<Input name="firm" id="firm-helper" />}
>
{choices.map((choice, index) =>
<MenuItem key={index} value={index} primaryText={choice} />
)}
</Select>
</Select>
<FormHelperText>Select a Firm</FormHelperText>
</FormControl>
</form>
);
}
export default SimpleSelect;
I am currently getting this:
sed-vars
Line 39: Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
I do receive a JSON response of an array with the axios request. I'll also eventually need each form handelChange(event) to create a POST request to my endpoint.
Edit: I made an example using a public API - you need to have your component look something like this..
Live Demo/Example:
SimpleSelect
import React, { useEffect, useState } from "react";
import axios from "axios";
import { makeStyles } from "#material-ui/core/styles";
import {
FormControl,
Select,
FormHelperText,
Input,
InputLabel,
MenuItem
} from "#material-ui/core";
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap"
},
formControl: {
margin: theme.spacing(1),
minWidth: 250
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
function SimpleSelect() {
const classes = useStyles();
const [choices, setChoices] = useState([]);
const [selectedChoice, setSelectedChoice] = useState("");
useEffect(() => {
const fetchData = async () => {
const result = await axios(
"https://jsonplaceholder.typicode.com/todos?_limit=10"
);
setChoices(result.data);
};
fetchData();
}, []);
function handleChange(event) {
setSelectedChoice(event.target.value);
}
return (
<div>
<form className={classes.root} autoComplete="off">
<FormControl className={classes.formControl}>
<InputLabel htmlFor="firm-helper">Firm</InputLabel>
{/*
PLESE CONSOLE.LOG SO YOU CAN SEE WHAT IS IN YOUR API RESPONSE!!!
*/}
{console.log(choices)}
<Select
value={selectedChoice}
onChange={handleChange}
input={<Input name="firm" id="firm-helper" />}
>
{choices.map((choice, index) => (
<MenuItem key={index} value={choice}>
{choice.title}
</MenuItem>
))}
</Select>
<FormHelperText>Select a Firm</FormHelperText>
</FormControl>
</form>
{selectedChoice ? (
<div>
<h4>Selected Choice:</h4>
<pre>{JSON.stringify(selectedChoice, null, 2)}</pre>
</div>
) : (
""
)}
</div>
);
}
export default SimpleSelect;
Original Answer:
It looks like the issue has to do with how you are originally setting your choices state.. When your component is mounted, and you are waiting for data to arrive, you are using map like: choices.map(...) - which, would need to be: choices.choices.map(...), due to how you have your initial state set..
Changing to choices.choices.map(...) would get your component loaded, but you would again encounter that same error after your data has arrived, this is due to how you are setting choices inside of useEffect...see below for more info, but you would need to change useEffect to: setChoices({ choices: data.results })
If you do not want to use choices.choices - all you have to do is change:
This:
const [choices, setChoices] = React.useState({
choices: [],
})
To This:
const [choices, setChoices] = React.useState([])
If you want to keep using choices.choices, you will need to change:
The map from:
{choices.map((choice, index) =>
<MenuItem key={index} value={index} primaryText={choice} />
)}
To:
{choices.choices.map((choice, index) =>
<MenuItem key={index} value={index} primaryText={choice} />
)}
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
AND
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
The useEffect from:
React.useEffect(() => {
// other code here
setChoices(result.data);
})
To:
React.useEffect(() => {
// other code here
setChoices({ choices: result.data });
})
Also, as others have mentioned, you'll need to verify the contents of results.data are iterable, and can be mapped over..

Conditional Props in React (ACCEPT only 1 of 2 props)

I had a search bar component in react and I wanted to have either an onSubmit prop to it or an onChange prop, but not both of them together. How do I implement it in the best way possible?
I tried using if-else statements but the code doesn't look elegant to me.
class QueryBar extends PureComponent {
render() {
const { placeholder, leftIcon, onSubmit, onChange, width } = this.props;
return (
<form
style={{ width }}
onSubmit={e => {
e.preventDefault();
onSubmit(e.target[0].value);
}}
>
<InputGroup
placeholder={placeholder}
width={width}
leftIcon="search"
rightElement={
<Button
type="submit"
icon={leftIcon}
minimal={true}
intent={Intent.PRIMARY}
/>
}
/>
</form>
);
}
}
QueryBar.propTypes = {
width: PropTypes.number,
placeholder: PropTypes.string,
leftIcon: PropTypes.oneOfType(['string', 'element']),
onSubmit: PropTypes.func
};
QueryBar.defaultProps = {
placeholder: 'Search...',
leftIcon: 'arrow-right',
width: 360
};
export default QueryBar;
You can use the ternary operator (?) instead of if-else.
const { placeholder, leftIcon, onSubmit, onChange, width } = this.props;
const handleSubmit = onSubmit ? onSubmit : onChange;
And use the same function later
<form
style={{ width }}
onSubmit={e => {
e.preventDefault();
handleSubmit(e.target[0].value);
}}
>

How do I implement field validation for 'react-select'

I need a simple "required" validation for 'react-select' (github repo).
In the latest version it uses css-in-js approach. So I have custom styles:
export const customStyles = {
control: (base, state) => ({
...base,
}),
menu: (base, state) => ({
...base,
}),
menuList: (base, state) => ({
...base,
}),
}
How can I change e.g. borderColor if field is invalid?
On this point there's an issue opened on GitHub.
I see two different approaches:
the "lazy" one, where you change the border colour by adding a specific className. Example here.
As you want to custom the original select I would recommend to embed your customSelect in a separate file. Then you can pass a props isValid and use it to change your borderColor.
class CustomSelect extends React.Component {
render() {
const {
isValid
} = this.props
const customStyles = {
control: (base, state) => ({
...base,
// state.isFocused can display different borderColor if you need it
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red',
// overwrittes hover style
'&:hover': {
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red'
}
})
}
return <Select styles={ customStyles } {...this.props}/>
}
}
A help for someone who doesn't want to add all time some codes for only of this required validation in react-select. Just use react-hook-form-input.
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
Where this RHFInput from react-hook-form-input was just a save for me.. Complete example- react-hook-form-input.
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
];
function App() {
const { handleSubmit, register, setValue, reset, errors } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
<span className="text-danger">
{errors.reactSelect && errors.reactSelect.type === 'required' && "Please select"}
</span>
<button type="button">Reset Form</button>
<button>submit</button>
</form>
);
}
Hope, it will help someone like me as a beginner in react.
render.js
export const renderSelect = (props) => (
<div>
<Select
{...props}
value={props.input.value}
onChange={(value) => props.input.onChange(value)}
onBlur={() => props.input.onBlur(props.input.value)}
options={props.options}
key={props.input.value}
/>
{props.meta.touched && (props.meta.error && <p style={{ color: "red",fontSize:"12px" }}>{props.meta.error}</p>)}
</div>
);
implementForm.js
<Field
name="sex"
component={renderSelect}
options={Status}
isClearable={true}
validate={required}
/>
requiredFileMessage.js
const required = value => value ? undefined : 'Required'
The best way I found is to create a transparent input field that will be queried via javascript standard checkValidity. This should have absolute positioning and 100% width & height. You can then bind a listener to the input field for the invalid event created by checkValidity
Here is the code I use. There are alterations for use of value field as well as some styling (for MDB inputs) , but you can just change the classes of input to match your own styles libraries. In this manner your validation styling will be the same as your existing form inputs.
Hopes this helps someone.
/**************************************************************************************
*** Select - New Select Control Using react-select (to stop stupid probs with MDB) ***
**************************************************************************************/
// Use This progressively instead of InputSelect
/* Differences from native ReactSelect
Validation - Applies transparent input overlay that works with form checkValidity()
value: This is the record.field value not an object {label: x, value: y} as for react-select
grouped: Explicitly set grouped options when set true
objValue: Works the same as react-select value object
*/
// Note: value property works differently do react-select , use ObjValue to work same as react-select
export const Select = (props) => {
let { id, options, cols, value, objValue, label, grouped, ...o } = props
id = id ? id : 'react-select'
const [selected, setSelected] = useState(value)
const [invalid, setInvalid] = useState(false)
//DEFAULTS
if (!grouped) grouped = false
//--------------------------
// VALIDATION EVENT HANDLERS
//--------------------------
useEffect(() => {
//ADD EVENT HANDLER FOR FOR VALIDATION
let ele = document.getElementById(`${id}_invalid}`)
ele.addEventListener('invalid', (e) => {
console.log('e is ', selected, e)
if (typeof selected === 'undefined' || selected !== null) setInvalid(true)
})
//ON COMPONENT EXIT - REMOVE EVENT HANDLER
return () => {
ele.removeEventListener('invalid', () => {
setInvalid(false)
})
}
// eslint-disable-next-line
}, [])
//Value property (Allows Single field assignent) - translates to object in for {label:x, value:y}
useEffect(() => {
let val
if (grouped) {
val = _.findInGroup(options, 'options', (rec) => rec.value === value)
} else {
val = options.find((rec) => rec.value === value)
}
//console.log('Selected==>', val)
setSelected(val)
// eslint-disable-next-line
}, [value, options])
//objValue Property (Emulate standard react-select value object)
useEffect(() => {
if (objValue) {
setSelected(objValue)
}
// eslint-disable-next-line
}, [objValue])
//STYLING SAME AS MDB INPUT COMPONENTS
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
dropdownIndicator: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
}
const handleChange = (opt, i) => {
setSelected(opt)
//Callback function (i is used for nested data in record)
if (props && props.onChange) props.onChange(opt, i)
}
return (
<Col cols={cols}>
{label && <label className='tp-label text-uppercase'>{label}</label>}
<div className='select-wrapper'>
<ReactSelect
styles={customStyles}
value={selected ? selected : ''}
options={options}
onChange={(val, i) => handleChange(val, i)}
isSearchable={true}
{...o}
/>
<input
id={`${id}_invalid}`}
name={`${id}_invalid}`}
value={selected ? selected : ''}
onChange={() => {}}
tabIndex={-1}
className={`form-control tp-input w-100 ${invalid ? '' : 'd-none'}`}
autoComplete='off'
//value={selected}
onFocus={() => {
setInvalid(false)
}}
style={{
position: 'absolute',
color: 'transparent',
backgroundColor: 'transparent',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
}}
required={true}
/>
</div>
</Col>
)
}
look at this link:https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/index.js
you have to use Controller
<Controller
control={control}
rules={{ required: true }}
name="citySelect"
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
options={citiesOption}
value={city}
onChange={handleCity}
className="basic-single"
classNamePrefix="select"
isRtl
placeholder="استان مورد نظر"
as={Select}
/>
{errors.citySelect && <p>choose a city</p>}
React-Select style for Bootstrap 5 validation
class CustomSelect extends React.Component {
render() {
const { valid, invalid } = this.props;
let borderColor = '#ced4da';
let focusBorderColor = '#66afe9';
let focusBoxShadow = '0 0 0 .2rem rgba(0, 123, 255, 0.25)';
if (valid) {
borderColor = '#198754';
focusBorderColor = '#198754';
focusBoxShadow = '0 0 0 .2rem rgba(25, 135, 84, .25)';
} else if (invalid) {
borderColor = '#dc3545';
focusBorderColor = '#dc3545';
focusBoxShadow = '0 0 0 .2rem rgba(220, 53, 69, .25)';
}
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
borderColor: state.selectProps.menuIsOpen ? focusBorderColor : borderColor,
boxShadow: state.selectProps.menuIsOpen ? focusBoxShadow : 'none',
}),
};
return (
<Select styles={customStyles} {...this.props} />
);
}
}

Resources