React JS Material UI Autocomplete: Change Options - javascript

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;

Related

How to hide MUI Autocomplete Popper

I am trying to make a component which has the option of showing a history of selections or hiding the input. This is because the user may want to hide the data if they are in public but want the convenience of seeing past values if they aren't. I have the following component.
const SensitiveTextField: FunctionComponent<any> = ({ ...props }) => {
const [showData, setShowData] = useState(false);
useEffect(() => {
(async () => {
const storage = new ApplicationStorage();
const showFieldData = props.name
? await storage.getShowSensitiveFieldData(props.name)
: false;
setShowData(showFieldData ?? false);
})();
}, []);
const onVisibilityChange = () => {
setShowData((value) => {
const newValue = !value;
if (props.name) {
const storage = new ApplicationStorage();
storage.setShowSensitiveFieldData(props.name, newValue);
}
return newValue;
});
};
const renderInput = (params: any) => {
return (
<TextField
{...params}
type={showData ? 'text' : 'password'}
InputProps={{
...params.InputProps,
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={onVisibilityChange}
onMouseDown={onVisibilityChange}
>
{showData ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
);
};
return (
<Autocomplete {...props} renderInput={(params) => renderInput(params)} />
);
};
so the idea is that when showData is false, the input will be hidden and so will the options. I tried to do this by filtering the options to nothing but there is still an empty component that comes up. I tried to create a custom renderOptions that would just create an empty component but the same small bit showed up. The autocomplete uses the Popper component, is there a way to set that popper component to not render at all inside of the Autocomplete?
Here is where I invoke SensitiveAutocomplete
<SensitiveAutocomplete
name={item.key}
fullWidth
size="small"
onChange={onUserDefineValueChange(item.key)}
label={inputLabel(item.key)}
value={item.value}
options={item.history}
/>

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}
/>

Using Material-UI Autocomplete with useEffect

I'm trying to make a search component with Material-UI Autocomplete.
The issue I am facing is that the data from the API doesn't reload. So when I load, it fetch results, but then, if I change the input, it doesn't make another request.
I tried calling the api function in the onChange function, but it still doesn't do anything.
I am still new to React Hooks, any feedback will be greatly appreciated.
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 [input_var, setInput] = React.useState("");
const loading = open && options.length === 0;
const api = React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
var response = await fetch(`${baseUrl}/users?q=${input_var}`, {
method: "get",
headers: {
"Content-Type": "application/json",
Authorization: auth
}
});
await sleep(1e3); // For demo purposes.
const countries = await response.json();
console.log(countries);
countries.items.map((item) => console.log(item.name));
if (active) {
setOptions(countries.items);
}
})();
return () => {
active = false;
};
}, [loading, input_var]);
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={async (newValue) => {
await api;
await setInput(newValue);
}}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
)}
/>
);
}

Using axios to make use of a service not working

I'm trying to use S3 service to upload an image and it's telling me that certain variables aren't defined when I have defined them. I have imported axios and all the other stuff that are required
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import Card from "#material-ui/core/Card";
import TextField from "#material-ui/core/TextField";
import CreateIcon from "#material-ui/icons/Create";
import Box from "#material-ui/core/Box";
import CardMedia from "#material-ui/core/CardMedia";
import MuiAlert from "#material-ui/lab/Alert";
import Snackbar from "#material-ui/core/Snackbar";
import { withStyles } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
import Chip from "#material-ui/core/Chip";
import Avatar from "#material-ui/core/Avatar";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
import InputAdornment from "#material-ui/core/InputAdornment";
import { connect } from "react-redux";
function mapStateToProps(state) {
return {};
}
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
marginLeft: theme.spacing(15),
},
},
input: {
display: "none",
},
}));
const useSliderStyles = makeStyles({
root: {
width: 250,
},
input: {
width: 100,
},
});
const UploadButton = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<input
accept='image/*'
className={classes.input}
id='contained-button-file'
multiple
type='file'
/>
<label htmlFor='contained-button-file'>
<Button variant='contained' color='primary' component='span'>
Upload
</Button>
</label>
</div>
);
};
const StyledCard = withStyles({
root: { height: 600, width: 350 },
})(Card);
const PetitionForm = () => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [open, setOpen] = useState(false);
const [petition, validPetition] = useState(false);
const [noTitle, titleError] = useState(false);
const [noDescription, descriptionError] = useState(false);
const [hashtag, setHashtag] = useState("");
const [arrayOfHashtags, addHashtag] = useState([]);
const [money, setMoney] = React.useState(500);
const slider = useSliderStyles();
const handleTitleChange = (event) => setTitle(event.target.value);
const handleDescriptionChange = (event) => setDescription(event.target.value);
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
};
const Alert = (props) => (
<MuiAlert elevation={6} variant='filled' {...props} />
);
const clearField = (event) => {
setOpen(true);
if (title.length > 0 && description.length > 0) {
validPetition(true);
setTitle("");
setDescription("");
addHashtag([]);
setHashtag("");
axios({
url: `call/s3/backend`,
method: "post",
data: {
images: imageArray.toByteArray(),
},
})
.then((res) => {
imageUrlArr = res.data;
axios({
url: `api/petition_posts`,
method: "post",
data: {
petition_post: {
title: title,
description: description,
hashtags: arrayOfHashtags.join(" "),
amount_donated: 0,
media: imageUrlArr,
goal: money,
card_type: "petition",
org_profile_id: 1,
},
},
})
.then((res) => {
console.log(res.data);
})
.catch((error) => console.log(error));
})
.catch((error) => console.log(error));
}
titleError(true ? title.length === 0 : false);
descriptionError(true ? description.length === 0 : false);
};
const handleDelete = (h) => () => {
addHashtag((arrayOfHashtags) =>
arrayOfHashtags.filter((hashtag) => hashtag !== h)
);
};
const handleHashtagChange = (event) => setHashtag(event.target.value);
const handleSliderChange = (event, newValue) => {
setMoney(newValue);
};
const handleInputChange = (event) => {
setMoney(event.target.value === "" ? "" : Number(event.target.value));
};
const newHashtag = () => {
if (arrayOfHashtags.length < 3) {
addHashtag((arrayOfHashtags) => arrayOfHashtags.concat(hashtag));
} else {
console.log("Too many hashtags");
}
};
const Hashtags = arrayOfHashtags.map((h) => (
<Chip
key={h.length}
size='small'
avatar={<Avatar>#</Avatar>}
label={h}
onDelete={handleDelete(h)}
/>
));
return (
<StyledCard>
<Box mt={1}>
<Grid container justify='center'>
<TextField
id='outlined-multiline-static'
multiline
rows={1}
variant='outlined'
placeholder='Title'
value={title}
onChange={handleTitleChange}
helperText={
open // only displays helper text if button has been clicked and fields haven't been filled
? !noTitle || petition
? ""
: "Can't be an empty field"
: ""
}
/>
</Grid>
</Box>
<Box mt={1}>
<Grid container justify='center'>
<CardMedia title='Petition'>
<UploadButton />
</CardMedia>
</Grid>
</Box>
<div className={slider.root}>
<Typography>Amount to raise</Typography>
<Box>
<Grid container justify='center'>
<Slider
min={500}
max={10000}
value={typeof money === "number" ? money : 0}
onChange={handleSliderChange}
aria-labelledby='input-slider'
/>
<TextField
className={slider.input}
value={money}
onChange={handleInputChange}
InputProps={{
startAdornment: (
<InputAdornment position='start'>$</InputAdornment>
),
}}
helperText={
money < 500 || money > 10000
? "Please enter a value between 500 and 10000"
: ""
}
/>
</Grid>
</Box>
</div>
<Box mt={1} mb={1}>
<Grid container justify='center'>
<TextField
size='small'
inputProps={{
style: { fontSize: 15 },
}}
id='outlined-multiline-static'
multiline
rows={1}
placeholder='Hashtags'
variant='outlined'
value={hashtag}
onChange={handleHashtagChange}
helperText={
arrayOfHashtags.length === 3
? "You reached the maximum amount of hashtags"
: ""
}
/>
<Button color='primary' onClick={newHashtag}>
Create!
</Button>
{arrayOfHashtags.length > 0 ? Hashtags : ""}
</Grid>
</Box>
<Box mt={1} justify='center'>
<Grid container justify='center'>
<TextField
size='small'
inputProps={{
style: { fontSize: 15 },
}}
id='outlined-multiline-static'
multiline
rows={5}
placeholder='Description'
variant='outlined'
value={description}
onChange={handleDescriptionChange}
helperText={
// only displays helper text if button has been clicked and fields haven't been filled
open
? !noDescription || petition
? ""
: "Can't be an empty field"
: ""
}
/>
</Grid>
</Box>
<Box mt={1}>
<Grid container justify='center'>
<Button onClick={clearField}>
<CreateIcon />
Create Petition!
</Button>
{open && petition && (
<Snackbar open={open} autoHideDuration={2000} onClose={handleClose}>
<Alert severity='success'>
You have successfully create a petition!
</Alert>
</Snackbar>
)}
{open && !petition && (
<Snackbar open={open} autoHideDuration={2000} onClose={handleClose}>
<Alert severity='error'>You're missing one or more fields</Alert>
</Snackbar>
)}
</Grid>
</Box>
</StyledCard>
);
};
export default connect(mapStateToProps)(PetitionForm);
This is the error I'm getting, someone mentioned something about the scope and I think that shouldn't matter when I'm trying to assign a value to a variable as far I as know
Line 109:19: 'imageArray' is not defined no-undef
Line 113:11: 'imageUrlArr' is not defined no-undef
Line 123:24: 'imageUrlArr' is not defined no-undef
Search for the keywords to learn more about each error.

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>
)
}}
/>
)}
/>
);
}

Categories