问题描述
我正在构建一个使用redux表单和材料ui框架的嵌套表单框架-到目前为止,我已经在此处构建了组件-https://codesandbox.io/s/bold-sunset-uc4t5
我想做的-添加一个自动完成字段-仅在键入3个字符后显示可能的结果。
https://material-ui.com/components/autocomplete/
我希望它具有与文本字段类似的属性/样式,然后选择框
12月14日-最新表格框架 https://codesandbox.io/s/cool-wave-9bvqo
解决方法
解决方案在这里。
我创建了一个独立的表单组件来处理此自动完成查找。
-renderAutocompleteField。
import React from "react";
import TextField from "@material-ui/core/TextField";
import FormControl from "@material-ui/core/FormControl";
import Autocomplete from "@material-ui/lab/Autocomplete";
import { Box } from "@material-ui/core";
import CloseIcon from "@material-ui/icons/Close";
const renderAutocompleteField = ({input,rows,multiline,label,type,options,optionValRespKey,onTextChanged,onFetchResult,placeholder,fieldRef,onClick,disabled,filterOptions,meta: { touched,error,warning } }) => {
return (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<Autocomplete
freeSolo
forcePopupIcon={false}
closeIcon={<Box component={CloseIcon} color="black" fontSize="large" />}
options={options.map((option) =>
option[optionValRespKey]
)}
filterOptions={filterOptions}
onChange={(e,val) => {
onFetchResult(val);
}}
onInputChange={(e,val,reason) => {
onTextChanged(val);
}}
renderInput={(params) => (
<TextField
label={label}
{...params}
placeholder={placeholder}
InputLabelProps={placeholder? {shrink: true} : {}}
inputRef={fieldRef}
onClick={onClick}
disabled={disabled}
{...input}
/>
)}
/>
</FormControl>
);
};
export default renderAutocompleteField;
-AutocompleteFieldMaker.js
import React,{ Component } from 'react'
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
//import Button from '@material-ui/core/Button';
import { Field,Fields } from 'redux-form';
import renderAutocompleteField from "./renderAutocompleteField";
import Grid from '@material-ui/core/Grid';
import { getToken } from '../../_SharedGlobalComponents/UserFunctions/UserFunctions';
import { createFilterOptions } from "@material-ui/lab/Autocomplete";
//import './OBYourCompany.scss';
class AutocompleteFieldMaker extends Component {
constructor(props,context) {
super(props,context);
this.state = {
searchText: "",autoCompleteOptions: []
}
this.fetchSuggestions = this.fetchSuggestions.bind(this);
this.fetchContents = this.fetchContents.bind(this);
this.onTextChanged = this.onTextChanged.bind(this);
}
fetchSuggestions(value){
let that = this;
let obj = {};
obj[this.props.fields[0].name[0]] = value;
this.props.fields[0].initialValLookup(obj,this.props.fields[0].paramsforLookup,function(resp){
if(resp && resp.data && Array.isArray(resp.data)){
that.setState({
searchText: value,autoCompleteOptions: resp.data,lastOptions: resp.data
});
}
});
};
fetchContents(val){
let that = this;
let result = this.state.lastOptions.filter(obj => {
return obj[that.props.fields[0].optionValRespKey] === val
})
this.props.fieldChanged("autocomplete",result[0]);
};
onTextChanged(val) {
if (val.length >= 3) {
this.fetchSuggestions(val);
} else {
this.setState({ searchText: val,autoCompleteOptions: [] });
}
}
render() {
//console.log(",this.state.autoCompleteOptions",this.state.autoCompleteOptions)
return (
<div className="Page">
<Field
name={this.props.fields[0].name[0]}
label={this.props.fields[0].label}
component={renderAutocompleteField}
options={this.state.autoCompleteOptions}
optionValRespKey={this.props.fields[0].optionValRespKey}
placeholder={this.props.fields[0].placeholder}
//rows={item.type === "comment" ? 4 : null}
//multiline={item.type === "comment" ? true : false}
//onChange={(item.type === "slider" || item.type === "date" || item.type === "buttons")? null : (e,value) => {
// this.onHandle(e.target.name,value);
//}}
//onHandle={this.onHandle}
fieldRef={this.props.fields[0].fieldRef}
onClick={this.props.fields[0].onClick}
disabled={this.props.fields[0].disabled}
onTextChanged={this.onTextChanged}
onFetchResult={this.fetchContents}
filterOptions= {createFilterOptions({
stringify: (option) => option + this.state.searchText
})}
/>
</div>
)
}
}
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ },dispatch);
}
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(AutocompleteFieldMaker))
-AutocompleteFormShell.js
import React,{ Component } from 'react';
import { reduxForm } from 'redux-form';
import Button from '@material-ui/core/Button';
import AutocompleteFieldMaker from './AutocompleteFieldMaker';
class AutocompleteFormShell extends Component {
constructor(props,context);
this.fieldChanged = this.fieldChanged.bind(this);
this.submitBundle = this.submitBundle.bind(this);
this.state = {
bundle: ""
}
}
fieldChanged(field,value){
//console.log("Fields have changed",field,value);
let bundle = {}
bundle[field] = value;
this.setState({ bundle: bundle });
//if it doesn't have any submit buttons -- then submit the form on change of fields
if(!this.props.buttons.length > 0){
//console.log("submit the form as a buttonless form");
setTimeout(() => {
this.submitBundle();
},1);
}
}
isDisabled(){
let bool = false;
if(this.state.bundle === ""){
bool = true;
}
return bool;
}
submitBundle(){
this.props.onSubmit(this.state.bundle);
}
render(){
const { handleSubmit,pristine,reset,previousPage,submitting } = this.props
return (
<form onSubmit={handleSubmit}>
<AutocompleteFieldMaker fields={this.props.fields} fieldChanged={this.fieldChanged} />
<Button
variant={this.props.buttons[0].variant}
color={this.props.buttons[0].color}
disabled={this.isDisabled()}
onClick={this.submitBundle}
>
{this.props.buttons[0].label}
</Button>
</form>
)
}
}
export default reduxForm()(AutocompleteFormShell)
-AutocompleteForm.js
import React,{ Component } from 'react'
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Grid from '@material-ui/core/Grid';
import { uuid } from '../Utility/Utility';
// components
import AutocompleteFormShell from './AutocompleteFormShell';
import '../../../forms.scss';
import './AutocompleteForm.scss';
class AutocompleteForm extends Component {
constructor(props,context);
this.state = {
uuid: this.props.uuid? this.props.uuid : uuid(),theme: this.props.theme? this.props.theme : "light"
};
//if uuid is not supplied generate it. (uuid should be the same in a wizzardform)
//if theme is not provided default it to light (legible on white backgrounds)
this.submit = this.submit.bind(this);
this.validateHandler = this.validateHandler.bind(this);
this.warnHandler = this.warnHandler.bind(this);
}
submit(data) {
this.props.submitHandler(data);
}
validateHandler(values) {
const errors = {}
for (let i = 0; i < this.props.fields.length; ++i) {
let field = this.props.fields[i];
//loop through the field names -- checkbox will likely have more than 1
for (let j = 0; j < field.name.length; ++j) {
let fieldName = field.name[j];
if(field.validate !== undefined){
//create validation
if(field.validate.includes("email")) {
//email
if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values[fieldName])) {
errors[fieldName] = 'Invalid email address'
}
}
if(field.validate.includes("minLength")) {
//minLength
if (values[fieldName] !== undefined && values[fieldName].length < 3) {
errors[fieldName] = 'Must be 3 characters or more'
}
}
if(field.validate.includes("required")) {
//required
if (!values[fieldName] && typeof values[fieldName] !== "number") {
errors[fieldName] = 'Required'
}
}
}
}
}
return errors;
}
warnHandler(values) {
const warnings = {}
for (let i = 0; i < this.props.fields.length; ++i) {
let field = this.props.fields[i];
//loop through the field names -- checkbox will likely have more than 1
for (let j = 0; j < field.name.length; ++j) {
let fieldName = field.name[j];
if(field.warn !== undefined){
//create warn
//rude
if(field.warn.includes("git")) {
//required
if (values[fieldName] === "git") {
warnings[fieldName] = 'Hmm,you seem a bit rude...'
}
}
}
}
}
return warnings;
}
render() {
let errorPlaceholder = this.props.errorPlaceholder;
//light or dark theme for the form
return (
<div className={"Page form-components generic-form-wrapper " + this.state.theme}>
<Grid container spacing={1}>
<Grid item xs={12}>
{/*{this.state.uuid}*/}
<AutocompleteFormShell
initialValues={this.props.initialValues}
enableReinitialize={this.props.enableReinitialize? this.props.enableReinitialize: true}//allow form to be reinitialized
fields={this.props.fields}
buttons={this.props.buttons}
form={this.state.uuid}// a unique identifier for this form
validate={this.validateHandler}// <--- validation function given to redux-form
warn={this.warnHandler}//<--- warning function given to redux-form
onSubmit={this.submit}
previousPage={this.props.previousPage}
destroyOnUnmount={this.props.destroyOnUnmount}// <------ preserve form data
forceUnregisterOnUnmount={this.props.forceUnregisterOnUnmount}// <------ unregister fields on unmount
keepDirtyOnReinitialize={this.props.keepDirtyOnReinitialize}
/>
</Grid>
{errorPlaceholder && errorPlaceholder.length > 0 &&
<Grid item xs={12}>
<div className="error-text">
{errorPlaceholder}
</div>
</Grid>
}
</Grid>
</div>
)
}
}
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ },mapDispatchToProps)(AutocompleteForm))
,
我不是材料UI方面的专家,但我认为它只能帮助您进行样式设置。 我将尝试以广义的方式回答这个问题。 我假设您需要以下东西:
- 允许用户键入某些内容
- 在满足某些条件时调用API来获取建议。在这种情况下,只要输入值改变
- 对于您而言,我们还需要确保输入值的长度大于3
- 允许用户通过单击建议来设置参数值(这不会触发另一个API请求)
因此,我们需要将此信息保持在组件的状态。 假设您已设置了相关的redux slice,则您的组件可能如下所示:
const SearchWithAutocomplete = () => {
const [searchParam,setSearchParam] = useState({ value: '',suggestionRequired: false })
const onSearchParamChange = (value) => setSearchParam({ value,suggestionRequired: value.length > 3 /*This condition could be improved for some edge cases*/ })
const onSuggestionSelect = (value) => setSearchParam({ value,suggestionRequired: false }) //You could also add a redux dispatch which would reset the suggestions list effectively removing the list from DOM
useEffect(() => {
if(searchParam.suggestionRequired) {
// reset list of suggestions
// call the API and update the list of suggestions on successful response
}
},[searchParam])
return (
<div>
<input value={searchParam.value} onChange={event => onSearchParamChange(event.target.value)} />
<Suggestions onOptionClick={onSuggestionSelect} />
</div>
)
}
建议组件可能类似于:
const Suggestions = ({ onOptionClick }) => {
const suggestions = useSelector(state => state.suggestions)
return suggestions.length > 0 ? (
<div>
{suggestions.map((suggestion,index) => (
<div onClick={() => onOptionClick(suggestion)}></div>
))}
</div>
) : null
}