0

I use a controlled text field, in order to listen to the change of value in this field and to force the value in the case where the entry is requested in upper or lower case.

So I have to use the value property of the component state.

Unfortunately, in case of I am using this component in another Redux component and want to update this field through the properties connected to the global status of Redux, I don't know how.

If I use the properties of my component in order to value my controlled field, after the onChange of the field, I lose the modifications linked to the entry (because I do not value by value with the state).

If I use the state in the valuation of my controlled field, the value provided by the Redux props is simply not retrieved. Any ideas?

Simplified code snippets of my component:

import { TextField } from '@material-ui/core';
import React from 'react';
import { 
  MyInputProperties, 
  defaultInputFieldPropsValue
} from './MyInputProperties';
import { MyInputState } from './MyInputState';

export class MyInput
  extends React.Component<MyInputProperties, MyInputState> {

  static readonly VERSION: number = 100;

  public static defaultProps = defaultInputFieldPropsValue;

  constructor(props: MyInputProperties) {
    super(props);
    this.state = {
      ...this.state,
      value: this.props.value
    };
  }

  protected render(): JSX.Element {
    const {
      error,
      focused,
      helperText,
      label,
      required,
      type,
      textTransform,
      style,
      value: propValue
    } = this.props;

    const { value: stateValue } = this.state;

    const element = (
      <TextField
        inputProps={{ style: { textTransform: textTransform } }}
        id={this.id}
        disabled={this.isDisabled}
        error={error}
        focused={focused}
        helperText={helperText}
        label={label}
        onChange={this.handleChange.bind(this)}
        required={required}
        style={style}
        type={type}
        defaultValue={propValue}
        value={stateValue}
      />
    );

    return element;
  }

  private formatValue(value?: string): string | undefined {
    const { textTransform } = this.props;
    let curValue = value;
    if (curValue && textTransform) {
      if (textTransform === 'uppercase') {
        curValue = curValue.toUpperCase();
      } else if (textTransform === 'lowercase') {
        curValue = curValue.toLowerCase();
      }
    }
    return curValue;
  }

  /**
   * Callback fired when changing the value.
   * @param event Change event
   */
  private handleChange(event: React.ChangeEvent<HTMLInputElement>): void {
    const value = event.target.value;
    this.setState({ value: this.formatValue(value) });
  }
}
ecoplaneteer
  • 1,918
  • 1
  • 8
  • 29
Answerrer
  • 2,803
  • 4
  • 8
  • 18

2 Answers2

0

Rather than storing the value in a local state, you want your component to get and change its value via props. That way the component doesn't need to know or care whether the value is coming from redux or from another component's state.

You could keep a local state if you wanted to, but you would want props.value to be the single source of truth and update the local state if props.value changes.

I recommend that you add a prop setValue which will be a void function that takes a string. When the TextField changes, we will apply the correct format to the new value and then call setValue with the new formatted value.

A few sidenotes about your code:

  • You can use the spread operator to pass lots of props to TextField at once rather than assigning them all individually
  • You should not assign the element to const element. Just return it directly.

Here's a simple version as a function component. This component takes all valid props of TextField and passes them through. We enforce the casing on every change of the input, but note that we don't check the case of the initial value or if it changes due to something external.

import React from "react";
import { TextField, TextFieldProps } from '@material-ui/core';

type MyInputProperties = TextFieldProps & {
  textTransform?: "uppercase" | "lowercase";
  setValue(value: string): void;
  value?: string;
}

export const CaseEnforcingInput = ({value = '', setValue, textTransform, ...props}: MyInputProperties) => {

  const formatValue = (value?: string): string | undefined => {
    let curValue = value;
    if (curValue && textTransform) {
      if (textTransform === 'uppercase') {
        curValue = curValue.toUpperCase();
      } else if (textTransform === 'lowercase') {
        curValue = curValue.toLowerCase();
      }
    }
    return curValue;
  }

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    const value = event.target.value;
    setValue( formatValue(value) );
  }
  
  return (
    <TextField
      {...props} // pass though all other props
      onChange={handleChange}
      value={value}
      //inputProps={{ style: { textTransform: textTransform } }}
  />
  )
};

We can use componentDidMount/useEffect to call setValue with a properly formatted value if props.value doesn't match the format. We also want to check when props.textTransform changes. We can check on changes of props.value to, but that one we need to be careful with because it's easy to create an infinite loop if not done correctly.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
0

Sorry I'm the one who screwed up. I don't need to store a state in my input. When I used a state, I had my field displayed with the value present in my props. Suddenly I lost the user input ... I'm stupid. Thanks Linda for your answer, it made me react.

Answerrer
  • 2,803
  • 4
  • 8
  • 18