Is there any way to use mui autocomplete with formik reusably? - javascript

I have a mui autocomplete component. I am trying to reuse this autocomplete component with a form where formik validation added.
My autocomplete component is,
const CustomAutoCompleteField = props => {
const {rerenderAutocomplete, data, refetchCategoryData, autoCompleteFieldsData, inputLabel, autoCompleteFieldsInputOnChange , onTouch, onErrors,fieldProps, onBlur} = props
const [textFieldData, setTextFieldData] = useState(null)
const onChangeHandler = (event, value) =>{
}
return (
<>
<Autocomplete
key={rerenderAutocomplete}
// value={onEdit && data}
isOptionEqualToValue={(option, value) => option.name === value.name}
onBlur={onBlur}
onChange={onChangeHandler}
fullWidth
id="tags-outlined"
options={autoCompleteFieldsData ? autoCompleteFieldsData : top100Films }
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (<TextField
required
{...params}
label={inputLabel}
onChange={textFieldInputOnChange}
error={Boolean(onTouch && onErrors)}
helperText={onTouch && onErrors}
{...fieldProps}
/>)}
/>
</>
);
};
Here I am passing formik attributes in side props which are, onTouch, onErrors,fieldProps, onBlur.
In My Parent component, i am using this autocomplete field by giving props, which are,
<CustomAutoCompleteField inputLabel='Select Category'
onBlur={addNewServiceFormik.handleBlur}
onTouch={addNewServiceFormik.touched.selectedCategoryName}
onErrors={addNewServiceFormik.errors.selectedCategoryName}
fieldProps={addNewServiceFormik.getFieldProps('selectedCategoryName')}
/>
I don,t know why, when i click submit on my form, this autocomplete doesn't show any helper text as per formik validation.

I simply created a component for this
In the form your must pass FormikProvider for use with FormikContext
import { TextField } from '#mui/material';
import Autocomplete from '#mui/material/Autocomplete';
import { useFormikContext } from 'formik';
import React from 'react';
type OptionsValues = {
title: string,
value: string
}
type Props = {
id: string,
name: string,
label: string,
options: OptionsValues[]
}
function MuiltSelect(props: Props) {
const { options, name, label, id } = props
const formik = useFormikContext();
return (
<Autocomplete
{...props}
multiple
options={options}
getOptionLabel={(option: any) => option.title}
onChange={(_, value) => formik.setFieldValue(name, value)}
filterSelectedOptions
isOptionEqualToValue={(item: any, current: any) => item.value === current.value}
renderInput={(params) => (
<TextField
{...params}
id={id}
name={name}
label={label}
variant={"outlined"}
onChange={formik.handleChange}
error={formik.touched[name] && Boolean(formik.errors[name])}
helperText={formik.errors[name]}
value={formik.values[name]}
fullWidth
/>
)
}
/>
)
}
export default MuiltSelect
check gist
https://gist.github.com/Wellers0n/d5dffb1263ae0fed5046e45c47a7c4a7

Related

Invalid Hook Call with react-hook-form

I am new to react hook form so this may be a simple issue. I just discovered that Controllers do not have the ability to use value as number. This was aggravating , but I eventually found this github issue #8068 which describes the solution as setting an onChange function like this:
<Controller
- rules={{ valueAsNumber: true }}
render={({ field }) => (
<input
- onChange={field.onChange}
+ onChange={(event) => field.onChange(+event.target.value)}
type="number"
/>
)}
/>
So I did some slight altering and came up with this code
import React, { ChangeEvent } from 'react'
import { Controller } from 'react-hook-form'
import { getPlaceholder } from './getPlaceholder'
import { IInput } from './types'
const NumberInput: React.FC<IInput> = ({ control, name, ...props }) => {
const placeholder = getPlaceholder({ type: "number" });
const numericalOnChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value === '') return null;
return +event.target.value;
}
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, ...field } }) => (
<input
{...props}
{...field}
type="number"
placeholder={placeholder}
onChange={(event) => {
const value = numericalOnChange(event)
onChange(value)
}}
className="h-[20px] pl-[4px] py-[8px] bg-transparent border-b
border-b-[#646464] focus:border-b-[#3898EC] text-[13px]
text-[#F00] placeholder-[#646464] outline-none m-1 w-full"
/>
)}
/>
)
}
export default NumberInput
which in theory should work, but ends up providing an Invalid Hook Call Error.

How can i change the both value change and function call in a single onChange

Here is the code, and i want to call apifetcher at the same time when the value of the city changes.how to do that?? is it possible
The value of the city should replace the 'q' value. And after that both the city and the API are passing to an another file.what should I add or remove.
import React, { useState } from "react";
import Cities from "./citylist";
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
import Content from "./content";
const SearchBar = () => {
const [city, setcity] = useState("");
const [api, setapi] = useState(
`http://api.openweathermap.org/data/2.5/forecast?q=Kurunegala,LK& mode=json&appid=5c4420d5c8a61c16e5ee37e4ca265763`
);
console.log(city);
Content(city, api);
const apiFtecher = () => {
return setapi(
`http://api.openweathermap.org/data/2.5/forecast?q=${city},LK&mode=json&appid=5c4420d5c8a61c16e5ee37e4ca265763`
);
};
return (
<div style={{ width: 300 }}>
<Autocomplete
freeSolo
id="free-solo-2-demo"
disableClearable
options={Cities.map((option) => option.name)}
renderInput={(params) => (
<TextField
{...params}
label="city"
margin="normal"
variant="outlined"
InputProps={{ ...params.InputProps, type: "search" }}
onChange={(e) => setcity(e.target.value)}
onBlur={(e) => setcity(e.target.value)}
/>
)}
/>
</div>
);
};
export default SearchBar;
There doesn't seem to be a need for the city state variable.
To call apiFtecher[sic] on every change of the input, you would do something like this:
const apiFtecher = e => {
const city = e.target.value;
return (
setapi(`http://api.openweathermap.org/data/2.5/forecast?q=${city},LK&mode=json&appid=5c4420d5c8a61c16e5ee37e4ca265763`)
);
}
And update the element to:
<TextField
{...params}
label="city"
margin="normal"
variant="outlined"
InputProps={{ ...params.InputProps, type: 'search' }}
onChange={apiFtecher}
/>

How to reset Autocomplete in react-hook-forms?

I'm working with react-hook-forms and trying to reset all form fields after submit. The problem is that in my case Autocomplete accepts objects as a value.
I've tried to set the defaultValue to {}, but received the following message:
Material-UI: the getOptionLabel method of Autocomplete returned undefined instead of a string for
{}
Are there any variants how Autocomplete could be reset?
Here is a link to the CodeSandbox
A little late to the party but here's a solution if anyone else needs it.
Assuming your autocomplete is wrapped inside a Controller, all you have to do is explicitly check the value in Autocomplete. Refer below for more
<Controller
name="category"
control={control}
rules={{ required: true }}
render={({ field }) => (
<Autocomplete
fullWidth
options={categories}
{...field}
// =====================================================
// Define value in here
value={
typeof field.value === "string"
? categories.find((cat) => cat === field.value)
: field.value || null
}
// =====================================================
onChange={(event, value) => {
field.onChange(value);
}}
renderInput={(params) => (
<TextField
{...params}
label={t("product_category")}
error={Boolean(errors.productCategory)}
helperText={errors.productCategory && "Product Category is required!"}
/>
)}
/>
)}
/>
This should do the trick!
To reset the value of AutoComplete with reset of react hook form you should add the value prop to the AutoComplete, other ways the reset wont function, see the example:
import { useEffect, useState } from 'react'
import {
Autocomplete,
TextField,
reset,
} from '#mui/material'
...
const {
...
setValue,
control,
formState: { errors },
} = useForm()
useEffect(() => {
reset({
...
country: dataEdit?.country ? JSON.parse(dataEdit?.country) : null,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataEdit])
...
<Controller
control={control}
name="country"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
value={value || null}
options={countries}
getOptionLabel={(option) => option.name}
onChange={(event, values) => {
onChange(values)
setSelectedCountry(values)
setValue('region', null)
}}
renderInput={(params) => (
<TextField
{...params}
label="Pays"
placeholder="Pays"
helperText={errors.country?.message}
error={!!errors.country}
/>
)}
/>
)}
/>

Reactjs - Update options by hitting API on every input change in Autocomplete component (Material UI)

I am a beginner in Reactjs. I want to create an Autocomplete component where on every input change the API is hit and the options are updated accordingly. I am using the Autocomplete component provided by Material UI. As I understand, the example given here hits an API once and filters it locally. I tried using the InputChange props provided by material component. Also I found this anser - https://stackoverflow.com/a/59751227/8090336. But can't figure out the right way.
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
import {CircularProgress} from "#material-ui/core";
import debounce from 'lodash/debounce';
const SelectField = ({inputLabel}) => {
const [ open, setOpen ] = React.useState(false);
const [ options, setOptions ] = React.useState([]);
const [ inputValue, setInputValue ] = React.useState("");
const loading = open && options.length === 0;
const onInputChange = debounce((event, value) => {
console.log("OnInputChange",value);
setInputValue(value);
(async() => {
const response = await fetch('https://api.tvmaze.com/search/shows?q='+inputValue);
console.log("API hit again")
let movies = await response.json();
if(movies !== undefined) {
setOptions(movies);
console.log(movies)
}
})();
}, 1500);
return (
<Autocomplete
style={{ width:300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionLabel={(option) => option.show.name}
onInputChange={onInputChange}
options={options}
loading={loading}
renderInput={(params) => (<TextField
{...params}
label={inputLabel}
variant="outlined"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} />: null }
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
}
export default SelectField;
I had faced this problem I manually called the API when ever the user types in. find the link for the sandbox. Check the onChange prop for the textfield rendered inside the autocomplete
// *https://www.registers.service.gov.uk/registers/country/use-the-api*
import fetch from "cross-fetch";
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import CircularProgress from "#material-ui/core/CircularProgress";
export default function Asynchronous() {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const loading = open && options.length === 0;
const onChangeHandle = async value => {
// this default api does not support searching but if you use google maps or some other use the value and post to get back you reslut and then set it using setOptions
console.log(value);
const response = await fetch(
"https://country.register.gov.uk/records.json?page-size=5000"
);
const countries = await response.json();
setOptions(Object.keys(countries).map(key => countries[key].item[0]));
};
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<Autocomplete
id="asynchronous-demo"
style={{ width: 300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={option => option.name}
options={options}
loading={loading}
renderInput={params => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
onChange={ev => {
// dont fire API if the user delete or not entered anything
if (ev.target.value !== "" || ev.target.value !== null) {
onChangeHandle(ev.target.value);
}
}}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
)}
/>
);
}

React JS Material UI Autocomplete: Change Options

I want to use an Autocomplete field for my React JS Project. For the design of the UI I use Material UI. In the documentation you can see the following example:
<Autocomplete
required
id="combo-box-demo"
filterOptions={(x) => x}
value={this.state.departure}
options={top100Films}
getOptionLabel={(option) => option.title}
renderInput={(params) => <TextField {...params} label="Startpunkt" variant="outlined" />}
/>
The options objects have the following default value:
let top100Films = [
{ title: 'The Shawshank Redemption', year: 1994 },
{ title: 'Monty Python and the Holy Grail', year: 1975 },
];
For my purpose I want to dynamically change the options since I use an Rest API where I get the results for the input. My question is therefore how I can change the options dynamically when the user is typing.
You can use onInputChange prop in your case:
<Autocomplete
required
id='combo-box-demo'
filterOptions={(x) => x}
value={this.state.departure}
options={top100Films}
getOptionLabel={(option) => option.title}
onInputChange={(event: object, value: string, reason: string) => {
if (reason === 'input') {
changeOptionBaseOnValue(value);
}
}}
renderInput={(params) => (
<TextField {...params} label='Startpunkt' variant='outlined' />
)}
/>
Then you can define changeOptionBaseOnValue to handle your options.
You can check this example:
import fetch from 'cross-fetch';
import React from 'react';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import CircularProgress from '#material-ui/core/CircularProgress';
function sleep(delay = 0) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
export default function Asynchronous() {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const loading = open && options.length === 0;
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
const response = await fetch('https://country.register.gov.uk/records.json?page-size=5000');
await sleep(1e3); // For demo purposes.
const countries = await response.json();
if (active) {
setOptions(Object.keys(countries).map((key) => countries[key].item[0]));
}
})();
return () => {
active = false;
};
}, [loading]);
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<Autocomplete
id="asynchronous-demo"
style={{ width: 300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={options}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
}
Source
I'm doing this as part of an address search/verification by using the OnChange in the text field with a handleAddressChange function that calls a findAddresses function. findAddresses uses Axios to make a call to an API, and then saves those results and displays them as the options for the results in the autocomplete.
Here's a simplified version of my code:
import React, { useState, ChangeEvent } from 'react';
import {
TextField,
InputAdornment
} from "#material-ui/core";
import Autocomplete from '#material-ui/lab/Autocomplete';
import { Search } from "#material-ui/icons";
import axios from "axios";
const AddressSearch = (props) => {
const [addressesList, setAddressesList] = useState([]);
const [inputAddress, setInputAddress] = useState<string>("");
const handleAddressChange = (event: ChangeEvent<{ value: unknown }>) => {
setInputAddress(event.target.value as string);
findAddresses(event.target.value as string);
};
const baseUrl = 'https://api.website.com/';
const findAddresses = (text?: string) => {
let params = `Key=value`
if (!!text) {
params += (`&Text=` + text);
let addressesResponse;
return (
axios.get(baseUrl + params)
.then(response => {
addressesResponse = response.data.Items
if (!Array.isArray(addressesResponse) || !addressesResponse.length) {
return;
}
setAddressesList(addressesResponse);
})
.catch(error => console.log(error))
)
}
}
return (
<div>
<Autocomplete
id="address-autocomplete"
freeSolo
options={addressesList}
getOptionLabel={(option) => option.Text}
popupIcon={<Search />}
renderInput={(params) => <TextField
id="address-input"
{...params}
onChange={handleAddressChange}
placeholder="Quickly find your address"
InputProps={{ ...params.InputProps,
startAdornment: (
<InputAdornment position="start"><Search /></InputAdornment>
)
}}
/> }
/>
</div>
);
}
export default AddressSearch;

Categories