How to get dynamically selected value from material ui autocomplete - reactjs

We can get TextField value dynamically with [evt.target.name]: evt.target.value but
How can I get Autocomplete selected value dynamically, Like bellow example:
import { useState } from 'react'
import Autocomplete from '#mui/material/Autocomplete'
import TextField from '#mui/material/TextField'
import Button from '#mui/material/Button'
const brands = ['niki', 'adidas', 'ramond', 'oliver', 'zara', 'casely']
const categories = ['pant', 'shirt']
const inputItems = [
{ label: 'Product Name', type: 'text', name: 'name' },
{ label: 'Brand', type: 'text', name: 'brand', options: brands },
{ label: 'Category', type: 'text', name: 'category', options: categories },
{ label: 'Price', type: 'number', name: 'price' },
{ label: 'Description', type: 'text', name: 'description' },
{ label: 'Image', type: 'text', name: 'image' },
]
const inputItemsObj = {}
inputItems.forEach(item => inputItemsObj[item.name] = '')
const Products = () => {
const [ fields, setFields ] = useState(inputItemsObj)
const [ fieldsError, setFieldsError ] = useState(inputItemsObj)
const changeHandler = (evt, newValue, action, option ) => {
setFields({ ...fields,[evt.target.name]: evt.target.value })
}
const submitHandler = (evt) => {
evt.preventDefault()
console.log(fields)
}
return (
<form noValidate onSubmit={submitHandler}>
{inputItems.map(({label, type, name, options}, key) => (name === 'category' || name === 'brand') ? (
<Autocomplete key={key}
options={options}
getOptionLabel={option => option}
renderInput={ params => <TextField {...params}
label={label}
placeholder={label}
type={type}
fullWidth
margin='dense'
InputLabelProps={{ shrink: true }}
name={name}
error={!fields[name] || !!fieldsError[name]}
helperText={fieldsError[name]}
/>}
name={name}
value={options[0]}
onChange={changeHandler}
/>
) : (
<TextField key={key}
label={label}
placeholder={label}
type={type}
fullWidth
autoFocus={key === 0}
margin='dense'
InputLabelProps={{ shrink: true }}
name={name}
value={fields[name]}
onChange={changeHandler}
multiline
rows={name === 'description' ? 3 : 1}
error={!fields[name] || !!fieldsError[name]}
helperText={fieldsError[name]}
/>
))}
<Button type='submit' variant='contained' >Submit</Button>
</form>
)
}
export default Products

The second value of onChange will give you a list of all the selected values.
function(event: React.SyntheticEvent, value: T | Array<T>, reason: string, details?: string) => void
So in your example use newValue rather than evt in your changeHandler
More info found here: https://mui.com/api/autocomplete/
Edit: I don't think you will be able to get the name from evt.target.name so some solution like this should work.
const changeHandler = (name) => (evt, newValue, action, option ) => {
// Also as you are updating/adding to state a callback should be used in setFields
setFields((prevState) => ({ ...prevState, [name]: newValue }))
}
Then in the Autocomplete:
<Autocomplete key={key}
//...other props
onChange={changeHandler(name)}
/>

Related

How to set default value of react-select after getting data from API

I have a react select component I want to update data so when I fetch data from my API i want to set my data to react select I use this code.
I have create state and use useEffect to fetch data from an API
const [currency, setCurrency] = useState([]);
useEffect(() => {
if (sessionStorage.getItem('AccountId')) {
axios.get(process.env.REACT_APP_SQL_API_URL + 'Organisation/' + query.search.split('=')[1])
.then(res => {
setCurrency({ label: res.data[0].currency, value: res.data[0].currency });
})
.catch(error => console.log(error.response))
}
else {
history.push('/login')
}
}, [])
This is the react select code
<Controller
rules={{ required: true }}
name="currency"
control={control}
render={({ field: { onChange } }) =>
<Select
onChange={(e) => {
onChange(e)
//handleSelect(e)
}}
options={[{ value: 'USD', label: 'USD $' }, { value: 'TZS', label: 'TZS' }]}
defaultValue={[currency]}
/>
}
/>
you need to have a state :
const [currency, setCurrency] = useState([]);
<Controller
rules={{
required: true }}
name="currency"
control={control}
render={({ field: { onChange } }) =>
<Select
onChange={(e) => {
onChange(e)
//handleSelect(e)
console.log(e.target);
console.log(e.target.value);
setCurrency(state=>[ //change state ])
}}
options={[{ value: 'USD', label: 'USD $' }, { value: 'TZS', label: 'TZS' }]}
defaultValue={currency}
value={currency}
/>
}
/>

My react-dropdown-select item is undefined

I am attempting to parse the dropdown item that is selected into a Prop. I get an error of
TypeError: Cannot read property 'name' of undefined
I have attempted to make a new Prop and parse it through there and that did not work either. I have also tried to add all array into the formData to no avail.
When I do not add the dropdown menu the Format and Location are parsed no problem, as soon as they change to the dropdown's it does not get read. I think the dropdowns change them into another format and not a string. .
The addFixture.js code is below:
const AddFixture = ({ addFixture, history }) => {
const [formData, setFormData] = useState({
teams: '',
opposition: '',
date: '',
format: '',
location: '',
});
const {
teams,
opposition,
date,
format,
location
} = formData;
const overs = [
{ value: '40 Overs', label: '40 Overs' },
{ value: '20 Overs', label: '20 Overs' },
]
const hOrA = [
{ value: 'Home', label: 'Home' },
{ value: 'Away', label: 'Away' },
]
const onChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
<form
className="form"
onSubmit={(e) => {
e.preventDefault();
addFixture(formData, history);
}}
>
<div className="form-group">
<Select
type="text"
placeholder="* Location"
name="location"
value={location}
options={hOrA}
onChange={onChange}
required
/>
</div>
<div className="form-group">
<Select
type="text"
placeholder="* format"
name="format"
value={format}
onChange={onChange}
options={overs}
required
/>
</div>
</form>
);
};
AddFixture.propTypes = {
addFixture: PropTypes.func.isRequired
};
React select expects the value to match one of the dropdown values - So
value={location} //needs to match the object {value:string, label:string}
React select passes in the full value of the selected option to the onChange so
onChange={onChange} //onChange={(val:{value:label}) => {}}
You can pass in the name of the dropdown like so
onChange={(val) => onChange(val,nameOfDropdown )}
Then use the name passed in like so
const onChange = (selected, name) =>{
setFormData({ ...formData, [name]: selected.value });
}
With all these changes your code should look something like this
const AddFixture = () => {
const [formData, setFormData] = useState({
format: '',
location: '',
});
const {
format,
location
} = formData;
const overs = [
{ value: '40 Overs', label: '40 Overs' },
{ value: '20 Overs', label: '20 Overs' },
]
const hOrA = [
{ value: 'Home', label: 'Home' },
{ value: 'Away', label: 'Away' },
]
/** onChange will get passed value from the dropdown {value:string, label:string} */
const onChange = (selected, name) =>{
setFormData({ ...formData, [name]: selected.value });
}
return(
<>
<div className="form-group">
<Select
type="text"
placeholder="* Location"
name="location"
/** Need to set this to one of the dropdown values {value:string, label:string} */
value={hOrA.find((val) => val.value === location)}
options={hOrA}
onChange={(val) => onChange(val,"location" )}
required
/>
</div>
<div className="form-group">
<Select
type="text"
placeholder="* format"
name="format"
/** Need to set this to one of the dropdown values {value:string, label:string} */
value={hOrA.find((val) => val.value === format)}
onChange={(val) => onChange(val,"format" )}
options={overs}
required
/>
</div>
</>
);
};

How can i validate multiple array controller with select option in react js?

I am doing simple an validation using react hook form but how can i take name after errors.name because i have an too complex name and i can't change that name cause of backend
Here is an Code sandbox link of my example
can anyone help me.
Here is my code:-
const App = () => {
const { control, handleSubmit, errors } = useForm();
const [target, setTarget] = useState([0]);
const addEmptyTargetFields = () => {
setTarget((previousTarget: any) => [...previousTarget, target.length + 1]);
};
const removeTarget = (index: number) => {
setTarget([...target.slice(0, index), ...target.slice(index + 1)]);
};
const onSubmit = () => {
console.log("form submitted");
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>Ice Cream Preference</label>
{target.map((e: any, i: number) => {
return (
<div key={i}>
<Controller
as={Select}
control={control}
name={`mediationRuleTargetList[${i}].networkId`}
placeholder="Bidder"
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
rule={{ required: true }}
/>
{errors.mediationRuleTargetList[${i}].networkId &&
errors.mediationRuleTargetList[${i}].networkId ===
"required" && <span>Bidders is required</span>}
<Controller
as={Select}
control={control}
name={`mediationRuleTargetList[${i}].ecpmLookBackPeriod`}
placeholder="eCPM look back period"
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
rule={{ required: true }}
/>
{errors.mediationRuleTargetList[${i}].ecpmLookBackPeriod &&
errors.mediationRuleTargetList[${i}].ecpmLookBackPeriod.type ===
"required" && <span>Bidders is required</span>}
{i > 0 && <button onClick={() => removeTarget(i)} />}
</div>
);
})}
<button onClick={() => addEmptyTargetFields()}>Add More</button>
<input type="submit" />
</form>
);
};

Using Material UI's Autocomplete using Formik to display different value in dropdown but set different value in formik state

I am trying to use Material UI's Autocomplete with Formik. Here is a custom Autocomplete component I wrote to use with Formik.
import React from "react";
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
import { fieldToTextField } from "formik-material-ui";
const FormikAutocomplete = ({ textFieldProps, ...props }) => {
const {
form: { setTouched, setFieldValue },
} = props;
const { error, helperText, ...field } = fieldToTextField(props);
const { name } = field;
return (
<Autocomplete
{...props}
{...field}
onChange={(_, value) =>
setFieldValue(name, value)
}
onBlur={() => setTouched({ [name]: true })}
renderInput={(props) => (
<TextField
{...props}
{...textFieldProps}
helperText={helperText}
error={error}
/>
)}
/>
);
};
export default FormikAutocomplete;
Here is how components callled
<Field
name="title"
component={FormikAutocomplete}
options={gender}
getOptionLabel={(option) => option.title_name_long}
textFieldProps={{
label: "Title",
required: true,
variant: "outlined",
margin: "dense",
}}
/>
Now what I intend to do is: If I have a object like
gender=[{gender_name_short:"F",gender_name_long:"Female},{gender_name_short:"M",gender_name_long:"Male}]
I want the Autocomplete dropdown to show options male,female.
But I want the formik state to save M,F respectively once selected from dropdown. Currently the entire object gets saved.
How can this be done?
In FormikAutocomplete component,
use setFieldValue in the onChange of autocomplete
use gender_name_long in getOptionLabel to display Male , Female
use gender_name_short in getOptionSelected to use M or F
With that, finally, when you submit, you will get to see M/F not Male/Female
Working demo
const gender = [
{ gender_name_short: "F", gender_name_long: "Female" },
{ gender_name_short: "M", gender_name_long: "Male" }
];
const validationSchema = object().shape({
// genderObj: array().required("At least one gender is required")
});
const initialValues = {
username: "abc",
country: "",
gender: "M",
birthdate: null
};
const FormikAutocomplete = ({ textFieldProps, ...props }) => {
const {
form: { setTouched, setFieldValue }
} = props;
const { error, helperText, ...field } = fieldToTextField(props);
const { name } = field;
return (
<Autocomplete
{...field}
{...props}
onChange={(_, data) => {
setFieldValue("gender", data.gender_name_short);
}}
onBlur={() => setTouched({ [name]: true })}
// getOptionLabel={item => item.gender_name_long} //<----see here
getOptionLabel={item => {
// console.log( '====>' , typeof item === "string" ? props.options.find(i => i.gender_name_short === item).gender_name_long : item.gender_name_long)
return typeof item === "string"
? props.options.find(i => i.gender_name_short === item)
.gender_name_long
: item.gender_name_long;
}}
// getOptionLabel={item => typeof item === 'string' ? props.options.find(i => i.gender_name_short === item).gender_name_long : item.gender_name_long}
getOptionSelected={(item, current) => {
return item.gender_name_short === current;
}}
// defaultValue={'hi'}
renderInput={props => (
<TextField
{...props}
{...textFieldProps}
helperText={helperText}
error={error}
/>
)}
/>
);
};
const SimpleFormExample = () => (
<div>
<h1>Simple Form Example</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
validateOnBlur={false}
validateOnChange
onSubmit={(values, { resetForm, setSubmitting }) => {
console.log(values);
resetForm();
// setSubmitting(false);
}}
>
{formik => (
<Form>
<Field
name="gender"
component={FormikAutocomplete}
label="gender"
options={gender}
textFieldProps={{
fullWidth: true,
margin: "normal",
variant: "outlined"
}}
// multiple
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
export default SimpleFormExample;

Material-UI Autocomplete event.target

I use Autocomplete component and I have code like this:
const countries = [
{country: 'Poland', code: 'PL'},
{country: 'Germany', code: 'DE'},
{country: 'Spain', code: 'ES'},
{country: 'France', code: 'FR'}
]
const Input = ({onCountryChange}) => (
<Autocomplete
id="combo-box"
options={countries}
getOptionLabel={option => option.country}
style={{ width: 300, marginTop: 10 }}
onChange={onCountryChange}
renderInput={params => (
<TextField {...params} label="Type a country" variant="outlined" fullWidth />
)}
/>
)
onCountryChange = (event) => {
this.setState({
countryCode: event.target.textContent
});
};
Now it sets as countryCode the country from countries options and I would like to use code instead (like PL). Any ideas how I can do this?
You have almost got the answer. I changed the onCountryChange function and look if this what you seek.
here is a working example: https://codesandbox.io/s/brave-mccarthy-kqx97
const countries = [
{ country: "Poland", code: "PL" },
{ country: "Germany", code: "DE" },
{ country: "Spain", code: "ES" },
{ country: "France", code: "FR" }
];
class App extends React.Component {
state = {
countryCode: ""
};
onCountryChange = (object, value) => {
this.setState({
countryCode: value.code
});
};
render() {
const { countryCode } = this.state;
return (
<div className="App">
{countryCode}
<Autocomplete
id="combo-box"
options={countries}
getOptionLabel={option => option.country}
style={{ width: 300, marginTop: 10 }}
onChange={this.onCountryChange}
renderInput={params => (
<TextField
{...params}
label="Type a country"
variant="outlined"
fullWidth
/>
)}
/>
</div>
);
}
}
From Material UI documentation:
function(event: React.SyntheticEvent, value: T | Array<T>, reason: string, details?: string) => void
event: The event source of the callback.
value: The new value of the
component. reason: One of "createOption", "selectOption",
"removeOption", "blur" or "clear".
This is my onChange function from Grouped template with TypeScript:
onChange={(event, value, reason, details) => selectChangeHandler(event, value as { title: string, year: number, firstLetter: string }, reason, details)}
const selectChangeHandler = (event: SyntheticEvent, value: { title: string, year: number, firstLetter: string }, reason: string, details: unknown) => {
event.preventDefault()
console.log('value', value)
console.log('reason', reason)
console.log('details', details)
}

Resources