frontend/app/common/UsersAutoComplete.tsx (81 lines of code) (raw):
import React, { ChangeEvent, useEffect, useMemo, useState } from "react";
import { TextField } from "@material-ui/core";
import { Autocomplete } from "@material-ui/lab";
import axios from "axios";
interface UsersAutoCompleteProps {
valueDidChange: (event: ChangeEvent<{}>, newValue: string[] | null) => void;
value: string;
label?: string;
shouldValidate?: boolean;
}
/**
* This component provides an auto-complete for known project owners
*/
const UsersAutoComplete: React.FC<UsersAutoCompleteProps> = (props) => {
const [userOptions, setUserOptions] = useState<string[]>([]);
const [inputValue, setInputValue] = useState("");
const [validationFailed, setValidationFailed] = useState(false);
const [userFieldValue, setUserFieldValue] = useState<string[]>([]);
useEffect(() => {
setUserFieldValue(props.value.split("|"));
}, [props.value]);
/**
* useMemo remembers a value for a given input and will not-recalculate it if the dependency does not change
* the factory function here performs a lookup for a prefix of `inputValue`.
* the promise rejects on failure
*/
const searchUsers = useMemo(() => {
return async () => {
const response = await axios.get<{ users: string[] }>(
`/api/valid-users?prefix=${encodeURIComponent(inputValue)}`
);
return response.data.users;
};
}, [inputValue]);
/**
* this effect is called when the user types something and is used to make the server request via a memoized callback
*/
useEffect(() => {
searchUsers()
.then((users) => setUserOptions(users))
.catch((err) => {
console.error(`Could not get valid users list: ${err}`);
if (err.response) console.log(err.response.data);
});
}, [inputValue, props.value, searchUsers]);
const validateEntry = async (newValue: string) => {
try {
const response = await axios.get<{ known: boolean }>(
`/api/known-user?uname=${encodeURIComponent(newValue)}`
);
setValidationFailed(!response.data.known);
} catch (err) {
console.error(`Could not validate user ${newValue}: `, err);
}
};
const inputDidChange = (evt: ChangeEvent<{}>, newValue: string | null) => {
setInputValue(newValue ?? "");
};
const keyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key == "Enter") {
if (props.shouldValidate) {
validateEntry(inputValue);
}
}
};
return (
<Autocomplete
multiple
freeSolo
autoComplete
includeInputInList
value={userFieldValue}
//onChange is fired when an option is selected
onChange={props.valueDidChange}
//onInputChange is fired when the user types
onInputChange={inputDidChange}
onKeyDown={keyPress}
options={userOptions}
renderInput={(params) => (
<TextField
{...params}
error={validationFailed}
helperText={
validationFailed
? "This name is not recognised, please check your typing. If you press the 'ENTER' key this value will be saved and recognised next time"
: ""
}
label={props.label}
/>
)}
/>
);
};
export default UsersAutoComplete;