reactjs - redux form and material ui framework - con auto type - e funzionalità di clearing field

Oct 07 2020

Sto costruendo un framework di moduli nidificati che utilizza il framework ui di redux form e material - finora ho costruito i componenti https://codesandbox.io/s/heuristic-hopper-lzekw

quello che vorrei fare - è aggiungere un po 'di "animazione" ai campi - per imitare la digitazione - ho ottenuto questo con una piccola funzione che prenderà il testo iniziale e passerà attraverso i caratteri - aggiornando il prop dell'iniziale valori per quel campo.

Ora il problema che ho - è che devo creare un onClick sul textField - e se è un campo typetext automatizzato - reimposta il valore su una stringa vuota - passa questo onclick indietro alle shell principali - e anche indietro fino alla funzione typetext per interrompere il timeout --- quindi se l'utente carica la pagina vede il testo che digita - ma con miglioramenti alla funzionalità dell'interfaccia utente - se faccio clic sul campo durante l'animazione - voglio che l'animazione si fermi / interrompa , Voglio che il campo si schiarisca.

Voglio avere il controllo anche su quali campi devono essere cancellati - quindi in questo caso - avere un parametro - che indica onClickClear: true - in modo da non interrompere i moduli pre-compilati del profilo di modifica dell'utente.

=== sandbox senza testo tipografico - ma una buona base per incollare questi due framework insieme https://codesandbox.io/s/heuristic-hopper-lzekw?file=/src/Home.js

== questa è l'ultima sandbox con la digitazione automatica come è attualmente 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}
  });
});

Risposte

1 lissettdm Oct 12 2020 at 17:54

Penso che aggiornare la proprietà segnaposto utilizzando il riferimento di input sia una buona soluzione, in questo modo non è necessario aggiornare il valore di input (evitare il rendering dei componenti) e puoi cancellare il testo segnaposto sull'evento clic:

Home.js

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

  componentDidMount() {
    var self = this;
    typeAnimation("Start typing...", 100, 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",
        label: "Search Term",
        name: ["search_term"],
        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,
  rows,
  multiline,
  label,
  type,
  meta: { touched, error, warning },
  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, timing, 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 // per mantenere visibile il segnaposto

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

#search-field::-webkit-input-placeholder {
  opacity: 1 !important;
}
1 ZacharyHaber Oct 12 2020 at 03:26

So che questa non è la risposta che stai cercando, ma il percorso più semplice sarebbe quello di animare il testo segnaposto invece del testo di input principale. Quindi non devi preoccuparti di nulla e puoi semplicemente lasciare che l'animazione si svolga indipendentemente dalle azioni dell'utente.

Ora il problema che ho - è che devo creare un onClick sul textField - e se è un campo typetext automatizzato - reimposta il valore su una stringa vuota - passa questo onclick indietro alle shell principali - e anche indietro fino alla funzione typetext per interrompere il timeout --- quindi se l'utente carica la pagina vede il testo che digita - ma con miglioramenti alla funzionalità dell'interfaccia utente - se faccio clic sul campo durante l'animazione - voglio che l'animazione si fermi / interrompa , Voglio che il campo si schiarisca.

Voglio avere il controllo anche su quali campi devono essere cancellati - quindi in questo caso - avere un parametro - che indica onClickClear: true - in modo da non interrompere i moduli pre-compilati del profilo di modifica dell'utente.

Tutto ciò è soddisfatto utilizzando invece il segnaposto del campo (anche se il testo non si interromperà perché non è necessario poiché il testo / il testo precompilato dell'utente nasconderà i segnaposto). L'unica cosa che non ho collegato è fermare il testo su Homedi esso componentWillUnmountpoiché senza di esso, verrebbe generato un messaggio di avviso che setState viene chiamato su un componente non montato.

Ho dovuto fare un po 'di refactoring perché c'erano alcuni problemi con cose come la modifica dello stato di React ( toggleFieldVisibilityin FieldMaker.js) e il non aggiornamento this.state.fieldsquando vengono trasmessi nuovi oggetti di scena poiché lo stato era impostato solo nel costruttore . Ho anche rinominato alcune cose all'interno FieldMaker.jsmentre ero lì (principalmente a causa delle preferenze personali in questo caso).

Ci sono sicuramente problemi con il tentativo di derivare lo stato dagli oggetti di scena indipendentemente da come lo fai: probabilmente non hai bisogno dello stato derivato

Codice in esecuzione:

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

Home.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", 100, (msg) => {
      this.setState({
        textPlaceholder: msg
      });
    });
    typeAnimation("I'm a value, but am I valuable??", 100, (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 },
        options: []
      },
      {
        type: "text",
        label: "Value",
        name: ["test2"],
        props: { placeholder: this.state.valPlaceholder }
      }
    ];

FieldMaker.js

Il getDerivedStateFromProps è la vera differenza principale qui, Questo è per popolare l'array dei sottotitoli in base ai campi ogni volta che i campi cambiano (e per impostare la visibilità). Non ho idea di quanto di che è veramente necessario in quanto non c'è alcuna nozione di ciò che qualsiasi di che è in realtà dovrebbe fare in questo. Quindi probabilmente ha bisogno di più lavoro per farlo funzionare completamente.

L'altra differenza è un po 'un refactoring nell'avere un visiblityoggetto separato nello stato piuttosto che modificare lo fieldsstato in.

Il motivo principale per modificare questo file è assicurarsi che gli aggiornamenti al fieldsprop si traducano in aggiornamenti per i figli in Fieldsmodo che il segnaposto possa essere trasmesso tramite props al Fielde quindi alrenderTextField

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

renderTextField.js

In questo, l'obiettivo della modifica è solo quello di passare il segnaposto al MUI TextField e di ridurre l'etichetta del MUI TextField impostando 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>
  );
};


Ho rifatto la soluzione in modo molto rapido e sporco per evitare le insidie ​​presenti nel file FieldMaker che inizialmente hanno causato problemi nella soluzione originale:

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

Ho modificato typeAnimation per supportare una parvenza di annullamento restituendo una funzione di annullamento che interrompe il ciclo e imposta utilizza il callback per impostare il valore sullo stato finale.

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

Quindi Home.js, ho modificato lo stato iniziale e il componentDidMount per lavorare con i segnaposto e darmi una posizione in cui memorizzare le funzioni di annullamento.

  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...",
      100,
      function (msg) {
        self.setState((state) => ({
          placeholders: { ...state.placeholders, search_term: msg }
        }));
      }
    );
  }

Aggiungo fieldsExtrase lo passo anche fino al componente FieldMaker per aggiungere oggetti di scena extra al campo in quel componente tramite l'indice che corrisponde fieldsSearchFormall'array.

    let fieldsExtras = [
      {
        placeholder: this.state.placeholders.search_term,
        onClick: this.cancelAnimations.search_term
      }
    ];

Quindi, una volta che gli oggetti di scena extra sono stati passati fino in fondo nel campo, in renderTextField, faccio lo stesso genere di cose di prima, ma ho anche aggiunto onClick per chiamare la onClickfunzione passata

const renderTextField = ({
  input,
  rows,
  multiline,
  label,
  type,
  meta: { touched, error, warning },
  placeholder,
  onClick,
  InputLabelProps
}) => {
  InputLabelProps = placeholder
    ? { ...(InputLabelProps ?? {}), shrink: true }
    : InputLabelProps;
  return (
    <FormControl
      component="fieldset"
      fullWidth={true}
      className={multiline === true ? "has-multiline" : null}
    >
      <TextField
        placeholder={placeholder}
        InputLabelProps={InputLabelProps}
        onClick={(e, value) => {
          onClick && onClick(e);
        }}
        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>
  );
};