redux表单和材料ui框架——自动完成字段



我正在构建一个使用redux表单和material 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))

--自动完成FormShell.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) {
super(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)

--自动完成窗体.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) {
super(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({ }, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AutocompleteForm))

我不是材质UI方面的专家,但我认为它只会帮助您进行造型。我只是试着用一种概括的方式来回答这个问题。我想你需要的东西是:

  • 允许用户键入某些内容
  • 调用API以在满足某些条件时获取建议。在这种情况下,只要输入的值发生变化
    • 在您的情况下,我们还需要确保输入值的长度大于3
  • 允许用户通过单击建议来设置参数值(这不应触发另一个API请求(

因此,我们需要将这些信息保留在组件的状态中。假设你已经设置了相关的redux切片,你的组件可能看起来像这样:

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
}

最新更新