reactjs-Redux表单和材料ui框架-具有自动类型-并具有清除字段功能

问题描述

我正在构建一个使用redux表单和材料ui框架的嵌套表单框架-到目前为止,我已经在此处构建了组件-https://codesandbox.io/s/heuristic-hopper-lzekw

我想做的-将一些“动画”添加到字段中-模仿打字-我已经通过一个小功能实现了这一点,该功能将使用初始文本并逐步处理字符-更新道具该字段的初始值。

enter image description here

我现在遇到的问题是-我需要在textField上创建一个onClick-如果它是一个自动的typetext字段-将值重置为空字符串-将此onclick传递回父外壳-甚至备份到typetext函数来打破超时---因此,如果用户加载页面,他们会看到文本输入-但UI功能得到了改进-如果我在动画中期单击该字段-我希望动画能够停止/中断,我想清除该字段。

我还希望控制应清除哪些字段-在这种情况下-具有一个参数-表示onClickClear:true-以免破坏用户编辑配置文件的预填写表格。

===没有类型文本的沙盒-但是是如何将这两个框架粘合在一起的良好基础 https://codesandbox.io/s/heuristic-hopper-lzekw?file=/src/Home.js

==这是当前具有自动键入功能的最新沙箱 https://codesandbox.io/s/amazing-bell-z8nhf

var self = this;
typeAnimation(this.state.initial_search_term.search_term,100,function(msg){
  self.setState({
    initial_search_term: {"search_term": msg}
  });
});

解决方法

我知道这不是您要查找的答案,但是最简单的方法是设置占位符文本的动画而不是主要输入文本。这样,您就不必担心任何事情,只要让动画播放就可以了,而与用户的操作无关。

现在的问题是-我需要在 textField-如果它是一个自动的typetext字段,则重置该值 到一个空字符串-将此onclick传递回父外壳 -甚至备份到typetext函数以打破超时--因此,如果用户加载页面,他们会看到文本输入-但使用UI 功能改进-如果我在 动画中期-我希望动画停止/中断,我希望该字段 清除。

我还想控制应该清除哪些字段-所以 在这种情况下-有一个参数-表示onClickClear:true-因此 以免破坏用户编辑个人资料的预填写表格。

所有这些都可以通过使用字段的占位符来满足(尽管输入文本不会停止,因为不需要,因为用户的文本/预填充文本会隐藏占位符)。我唯一没有想到的就是在Home的{​​{1}}上停止输入文本,否则,它将抛出警告消息,提示已在未安装的组件上调用setState。

我必须进行一些重构,因为mutating React state({{1}中的componentWillUnmount)存在一些问题,并且在新道具传递为state was only being set in the constructor。我还在toggleFieldVisibility内重命名了某些内容(主要是由于个人偏好)。

无论您如何操作,尝试从道具中获取状态肯定存在问题:You Probably Don't Need Derived State

运行代码:

https://codesandbox.io/s/busy-davinci-mk0dq?file=/src/Home.js

Home.js

FieldMaker.js

FieldMaker.js

这里的getDerivedStateFromProps是真正的主要区别,这是每当字段更改时,便根据字段填充subs数组(并设置可见性)。我不知道其中有多少是真正必要的,因为对此没有任何想法。因此,它可能需要更多的工作才能完全发挥作用。

另一个区别是重构了一个单独的this.state.fields对象,而不是修改FieldMaker.js状态。

修改此文件的主要原因是确保对 state = { initial_search_term: { search_term: "" },searchPlaceholder: "",textPlaceholder: "",valPlaceholder: "" }; componentDidMount() { typeAnimation("Search Text...",100,(msg) => { this.setState({ searchPlaceholder: msg }); }); typeAnimation("Just some super long text you used to know",(msg) => { this.setState({ textPlaceholder: msg }); }); typeAnimation("I'm a value,but am I valuable??",(msg) => { this.setState({ valPlaceholder: msg }); }); } // Render funct: let fieldsSearchForm = [ { type: "text",label: "Search Term",name: ["search_term"],props: { placeholder: this.state.searchPlaceholder },options: [] },{ type: "text",label: "Text",name: ["test"],props: { placeholder: this.state.textPlaceholder },label: "Value",name: ["test2"],props: { placeholder: this.state.valPlaceholder } } ]; 道具的更新转换为对子项visiblity的更新,以便可以通过道具将占位符向下传递到{{1 }}和fields

fields

renderTextField.js

在这种情况下,更改的目的只是将占位符向下传递到MUI TextField,并通过设置Fields

使MUI TextField的标签缩小
Field


我以一种非常肮脏的方式重新解决了这个问题,避免了FieldMaker文件中存在的陷阱,这些陷阱最初会在原始解决方案中引起问题:

https://codesandbox.io/s/fervent-moser-0qtvu?file=/src/Home.js

我修改了typeAnimation,通过返回取消函数来支持某种取消效果,该函数停止循环并设置使用回调将值设置为结束状态。

renderTextField

然后在 state = { visibility: {} }; static getDerivedStateFromProps(props,state) { let newState = { prevFields: props.fields }; if (props.fields !== state.prevFields) { let visibility = state.visibility; let subs = props.fields.reduce((subs,field) => { if (field.sub) { subs.push(field.sub); visibility[field.name] = false; } else { visibility[field.name] = true; } return subs; },[]); newState.subs = subs; } return newState; } toggleFieldVisibility(pos,isVisibile) { let field = this.props.fields[pos].name; this.setState((prev) => { return { ...prev,[field]: isVisibile }; }); // This directly manipulates state,and is likely problematic in React // let fields = { ...this.state.fields }; // fields[pos]["visibility"] = isVisibile; } componentDidMount() { this.hideSubs(); } // In render: return ( <> {this.props.fields.map((item,j) => { if (this.state.visibility[item.name]) { if (item.type !== "checkbox") { return ( <Field key={j} name={item.name[0]} props={item.props} label={item.label} // ... 中,我修改了初始状态和componentDidMount以使用占位符,并给了我一个存储取消功能的位置。

InputLabelProps = {shrink: true}

我还添加了const renderTextField = ({ input,rows,multiline,label,type,meta: { touched,error,warning },placeholder,InputLabelProps }) => { // Ensure that the label is shrunk to the top of the input // whenever there's a placeholder set InputLabelProps = placeholder ? { ...(InputLabelProps ?? {}),shrink: true } : InputLabelProps; return ( <FormControl component="fieldset" fullWidth={true} className={multiline === true ? "has-multiline" : null} > <TextField InputLabelProps={InputLabelProps} placeholder={placeholder} label={label} multiline={multiline} rows={rows} type={type} error={touched && (error && error.length > 0 ? true : false)} helperText={ touched && ((error && error.length > 0 ? error : null) || (warning && warning.length > 0 ? warning : null)) } {...input} /> </FormControl> ); }; ,并将其一直传递到FieldMaker组件,以通过与export function typeAnimation(text,timing,callback) { let concatStr = ""; let canceled = false; function cancel() { canceled = true; } async function runAnimation() { for (const char of text) { concatStr += char; await sleep(timing); if (canceled) { break; } callback(concatStr); } if (canceled) { callback(text); } } runAnimation(); return cancel; } 数组匹配的索引向该组件中的Field添加额外的道具。

Home.js

然后,一旦额外的道具一直传递到字段中,在 constructor(props,context) { super(props,context); this.state = { initial_search_term: { search_term: "" },placeholders: { search_term: "" } }; } cancelAnimations = {}; componentDidMount() { var self = this; this.cancelAnimations.search_term = typeAnimation( "Start typing...",function (msg) { self.setState((state) => ({ placeholders: { ...state.placeholders,search_term: msg } })); } ); } 中,我将执行与以前相同的操作,但是我还添加了onClick来调用传递的{{1 }}功能

fieldsExtras
,

我认为使用input ref更新占位符属性是一个很好的解决方案,这样您就不需要更新输入值(避免重新渲染组件),并且可以在click事件中清除占位符文本:

Home.js

class Home extends Component {
  constructor(props,context);
    this.searchInputRef = React.createRef(null);
    this.state = { initial_search_term: { search_term: "" } };
  }

  componentDidMount() {
    var self = this;
    typeAnimation("Start typing...",function (msg) {
      if (document.activeElement !== self.searchInputRef.current) {
        self.searchInputRef.current.setAttribute("placeholder",msg);
      } else {
        return true; // stop typings
      }
    });
  }


  render() {
    //...

    let fieldsSearchForm = [
      {
        id: "search-field",type: "text",options: [],fieldRef: this.searchInputRef,onClick: () => (this.searchInputRef.current.placeholder = "")
      }
    ];
   //...
  }
}

FieldMaker.js

class FieldMaker extends Component {
  //...

  
  render() {
   
    return (
      <>
        {this.state.fields.map((item,j) => {
          if (item.visibility) {
            if (item.type !== "checkbox") {
              return (
                <Field
                  id={item.id}
                  //...other props
                  fieldRef={item.fieldRef}
                  onClick={item.onClick}
                />
              );
            } else {
              //...
            }
          } else {
            //...
          }
        })}
      </>
    );
  }
}

renderTextField.js

const renderTextField = ({
  id,input,onClick,fieldRef
}) => (
  <FormControl
    component="fieldset"
    fullWidth={true}
    className={multiline === true ? "has-multiline" : null}
  >
    <TextField
      id={id}
      inputRef={fieldRef}
      onClick={onClick}
      // other props
    />
  </FormControl>
);

Utility.js

export async function typeAnimation(text,callback) {
  let concatStr = "";
  for (const char of text) {
    concatStr += char;
    await sleep(timing);
    const shouldStop = callback(concatStr);
    if (shouldStop) break; // stop the loop
  }
}

styles.css //使占位符可见

#search-field-label {
  transform: translate(0,1.5px) scale(0.75);
  transform-origin: top left;
}

#search-field::-webkit-input-placeholder {
  opacity: 1 !important;
}