Google Places (multiple) - reactjs

I am wanting users to be able to input multiple locations: for example
Melbourne, Perth, Sydney.
I am currently using
<input id="searchTextField"></input>
<script type="text/javascript">
function initialize() {
var options = {
componentRestrictions: {country: 'au'}
};
var input = document.getElementById('searchTextField');
var autocomplete = new google.maps.places.Autocomplete(input, options);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
However I want to move it to Material-UI Autocomplete - while yes it works it won't allow me to select the location due to Autocomplete requires options, and without that it won't work. I am wondering what is a better way to get multiple locations working.
<Autocomplete
id="searchTextField"
name="country"
options={countries}
required
multiple
variant="outlined" fullWidth
getOptionLabel={option => option.label}
// style={{ width: 300 }}
onChange={(e, value) => {
console.log(value);
setFieldValue(
"country",
value !== null ? value : initialValues.country
);
}}
renderInput={params => (
<TextField
margin="normal"
label="Country"
variant="outlined"
fullWidth
required
name="country"
{...params}
/>
)}
/>
I have also tried the following but when a user clicks it does not set to the Field.
<Field name="targetLocation" >
{({ field, form, meta }) => (
<div>
<input variant="outlined" id="searchTextField" fullWidth
type="text" {...field} required label="Campaign Name"/>
{meta.touched &&
meta.error && <div className="error">{meta.error}</div>}
</div>
)}
</Field>
DEMO of issue - you'll notice only 1 input is being sent to array.
https://codesandbox.io/s/youthful-hofstadter-dp4mx?file=/src/App.js

The problem was syncing the GoogleAutoComplete component when the state is being updated. So there was no initial value for the autocomplete.
Here is the working code.
https://codesandbox.io/s/kind-monad-cz60f?file=/src/App.js

Related

Create tag using Autocomplete material UI when user input anything

I can type something which shows selected tags from the dropdown list but I want a user can type something and create a tag or multiple tags separated by a comma.
I used a useState hook which is an array.
const [tags, setTags] = useState([]);
I set the Autocomplete like the following code -
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
onChange={(e) => setTags([...tags, e.target.value.split(",")])}
/>
)}
/>;
Surprisingly, I tried for an hour before questioning. But Solve it within a few moments after putting the question.
Here's the solution-
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[...tags]}
freeSolo
autoSelect
onChange={(e) => setTags([...tags, e.target.value])}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
/>
)}
/>;
the output will look like this.
user input tag
but I want to add multiple tags that didn't happen right now which can be put via giving a space or comma.
Partly relevant but I think it can be helpful for someone using React Hook Form library with MUI5.
Storing tags in the state would render them being saved in form data when submitted. Instead, you need to use their onChange function.
const Tags = ()=>{
const { control,handleSubmit } = useForm({
defaultValues: {
checkbox: true,
autocomplete:["test"]
}
});
const onSubmit = data => console.log(data);
return <>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="autocomplete"
control={control}
render={({ field }) => {
const {value,onChange} = field
return <Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={value}
defaultValue={[...value]}
freeSolo
autoSelect
onChange={((e)=>onChange([...value,e.target.value]))}
renderInput={(params) => {
return <TextField
{...params}
label="Tags"
placeholder="Tags"
value={value}
/>
}}
></Autocomplete>
}
}
/>
<input type="submit" />
</form>
</>
}

Material UI free solo Autocomplete, how to submit data?

I am new to Material UI and I wonder how it is possible to submit data with a free solo autocomplete component, also with a TextField. My goal is ultimately to push the router to a new page, after the search result.
I think that the code sandbox shows a more clear example:
Code Sandbox
You can just use the onChange of the autocomplete instead of tracking it with a onChange of the textfield:
export default function App() {
function handleSubmit(event, value) {
event.preventDefault();
console.log("Country:", value);
}
return (
<div className="App">
<h1>Material AutoComplete</h1>
<h2>How to get this to submit?</h2>
<div>
<Autocomplete
freeSolo
id="autocomplete"
disableClearable
options={allCountries.map((option) => option.countryname)}
onChange={handleSubmit} // This will be called on selection of the country
renderInput={(params) => (
<TextField
{...params}
margin="normal"
aria-label="enter search"
name="search"
placeholder="Search"
// No need to check the onChange here
InputProps={{
...params.InputProps,
startAdornment: <SearchIcon />,
type: "search"
}}
/>
)}
/>
</div>
</div>
);
}

Cypress select first item of Autocomplete Material UI

I have a react hook forms that contains Autocomplete material UI controls.
<Controller
as={
<Autocomplete
data-cy="profileCountry"
options={countries}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
{...params}
label="* Country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
}
rules={{ required: true }}
onChange={([, data]) => data}
defaultValue={{ id: 0, name: "" }}
getOptionSelected={(option, value) => option.id === value.id}
name="country"
id="country"
control={control}
/>
I want to run a cypress test case to fill up the form and submit. How can I select like the first option in this component using cypress.
Currently I just tried my luck like the following.
cy.get("[data-cy=profileCountry]").select("Germany");
I used this and it works:
cy.get('.MuiAutocomplete-popper li[data-option-index="0"]').click();
Add a custom command:
Cypress.Commands.add('getDropdownOptions', () => {
return cy.get('.MuiAutocomplete-popper [role="listbox"] [role="option"]', {
timeout: 10000,
});
});
Then you can simply...
cy.getDropdownOptions().contains('Germany').click();
cy.get("#combo-box").click();
cy.get("li[data-option-index="0"]").contains("ntest_user").then((option)=>
{
option[0].click();
})
You need the follwing:
cy.wait(2000) // waiting for ajax to complete (till the request is resolved)
cy.get('#The MUI ID').click({force:true}).focused().type('Germany');
cy.contains('Germany').click({force:true});
Assuming we give an id to the autocomplete such as
id="pool-leg-autoComplete"
freeSolo
options={legOptions.map((option) => option.title)}
onChange={updateNewLegItemText}
renderInput={(params: any) => (
<TextField
{...params}
label="Add a leg to the pool"
margin="normal"
autoFocus
variant="outlined"
value={newLegItemText}
/>
cy.get('#pool-leg-autoComplete-option-0').click();
Where the index of the item is 0 in this case.

React Form Hook with Autocomplete Material UI

I have a countries array which contains id and name. Currently I am using a Material UI Autocomplete element and I have a react hook form. When I submit the form, I want to fetch the country Id. Currently it is posting the country name. Is there a way to post the ids instead of the names without going and fetching the id from the name.
<Autocomplete
className="form-item"
options={countries}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
{...params}
inputRef={register}
label="Country"
name="country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
Use react-hook-form's Controller and put the entire Autocomplete in the as prop. With this, when you submit the form you will get the entire object of the selected option.
Note: In react-hook-form version 6.x, the onChange is removed, the as prop will take a function and you can obtain onChange as param.
Working demo - v6
<Controller
as={({ onChange }) => (
<Autocomplete
className="form-item"
options={countries}
onChange={(_, data) => onChange(data)}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
label="Country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
)}
name="country"
control={control}
defaultValue={{ label: "The Shawshank Redemption", id: "1994" }}
/>
Note: If you are using v5x then see demo and code snippet below.
Working demo - v5
<Controller
as={
<Autocomplete
className="form-item"
options={countries}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
label="Country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
}
name="country"
control={control}
onChange={([, data]) => data}
defaultValue={{ label: "The Shawshank Redemption", id: "1994" }}
/>
Edit: based on comment
You can use setValue to set default values based on an api.
code snippet:
useEffect(() => {
setTimeout(() => { // fake api
setValue(
"country",
{ label: "hi The Godfather", id: "1972" },
{ shouldDirty: true }
);
}, 2000);
}, []);
Demo v6 above is updated.
Also see official demo of setValue usage here
Here is a simplest way to create it, render your Autocomplete component inside Controller from react hook from, use onChange and value from the render function to control the value
<Controller
control={control}
name="type"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo
options={['field', 'select', 'multiple', 'date']}
onChange={(event, values) => onChange(values)}
value={value}
renderInput={(params) => (
<TextField
{...params}
label="type"
variant="outlined"
onChange={onChange}
/>
)}
/>
)}
I'm afraid that there is not an 'easy' way to get the ids with the current setup.
However, you can hook into the Autocomplete#onChange event and use the value prop to take over the internal value state of the Autocomplete component. This results in having the value available in your component and use this to your advantage.
In the example below, the id will be available in the form data as country-id.
function CountryAutocomplete() {
const [value, setValue] = useState(); // `[]` for multiple
return (
<React.Fragment>
<Autocomplete
className="form-item"
value={value}
onChange={(event, value) => setValue(value)}
options={countries}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
{...params}
inputRef={register}
label="Country"
name="country"
placeholder="Select a Country"
InputLabelProps={{
shrink: true
}}
variant="outlined"
/>
)}
/>
<input value={value?.id} name="country-id" />
</React.Fragment>
);
}

react- cleaning autocomplete generates an error

I have a form created with material-ui, I have a textfield that uses autocomplete which is filled with a useEffect that is responsible for bringing the data from the API to load the AUTOCOMPLETE.
The data is not saved.
Code
const defaultProps={
options:location.name,
getOptionLabel: option => option.label + "-" + option.phone,
};
<Autocomplete
style={{ width: 300 }}
{...defaultProps}
id="city"
autoComplete
onChange={(event, newVal)=>onChange({target:{name:"name",value: newVal.label }}
)}
renderInput={params => (
<TextField {...params} label="Country" margin="normal" variant="outlined" inputProps={{
...params.inputProps,
}}/>
)}
What is causing that behavior?
It appears your problem in on the following line:
onChange={(event, newVal)=>onTagsChange({target:{name:"label",value: newVal.label }}
It may be related to newValue being null after clearing. You could add a protection such as:
value: newVal || newVal.label
Since it is not clear to me how you are using the resulting selection my suggested protection may not be appropriate.
You may also want to take a look at their "controlled" example in the documentation (https://material-ui.com/components/autocomplete/#playground) in which they use the value attribute to control the AutoComplete:
<Autocomplete
{...defaultProps}
id="controlled-demo"
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
renderInput={params => (
<TextField {...params} label="controlled" margin="normal" fullWidth />
)}
/>

Resources