2

I am new to React.js, but am attempting to put together a simple application using TypeScript /TSX. The application contains a form that requires validation. I had it working by assigning handlers to each input and updating state accordingly, but this is a bit cumbersome (even for the small form I am creating):

export interface AddInventoryRowProps {
    catOptions : Array<any>,
    conditionOptions: Array<any>
}

export interface AddInventoryState {
    title: string,
    sku: string,
    mpn: string,
    catID : string,
    conditionID: string,
    price: string,
    isCategoryValid : boolean,
    isMPNValid : boolean,
    isSKUValid : boolean,
    isTitleValid : boolean,
    isPriceValid : boolean,
    isConditionValid : boolean

}

export class AddInventoryRow extends React.Component<AddInventoryRowProps, AddInventoryState> {
    constructor(props : AddInventoryRowProps) {
        super(props);
        this.state = { title: "", 
        sku: "", 
        mpn: "", 
        catID: "",
        price: "", 
        conditionID: "",
        isCategoryValid: false,
        isMPNValid: false,
        isPriceValid: false,
        isSKUValid: false,
        isTitleValid: false,
        isConditionValid: false}

        this.handleCatChange = this.handleCatChange.bind(this);
        this.handleConditionChange = this.handleConditionChange.bind(this);
        this.handlePriceChange = this.handlePriceChange.bind(this);
        this.handleSKUChange = this.handleSKUChange.bind(this);
        this.handleSubmitClick = this.handleSubmitClick.bind(this);
        this.handleMPNChange = this.handleMPNChange.bind(this);
       this.handleTitleChange = this.handleTitleChange.bind(this);
    }

Every "handle" function would look something like this:

    handleMPNChange(ev : ChangeEvent<HTMLInputElement>) {
    this.setState( {isMPNValid: (ev.currentTarget as HTMLInputElement).value != "" });
    this.setState( {mpn: (ev.currentTarget as HTMLInputElement).value });
}

with a corresponding input element in the render() function:

   <div className="form-group">
   <label>UPC/MPN</label>
   <input name="mpn" onChange={this.handleMPNChange} value={this.state.mpn} id="mpn" 
 className={this.state.isMPNValid ? "form-control form-control-sm" 
 : "form-control form-control-sm error is-invalid" } />
   </div>

I would like to make a single input handler, similar to the example from the React website. However, due the TypeScripts's "typing", the following does not work in my .txs file:

handleChange(ev : ChangeEvent<HTMLInputElement>) {
    const target : HTMLInputElement = ev.currentTarget as HTMLInputElement;
            const name : string = target.name;
            const value: string = target.value;
            this.setState( {[name]: value});
}

To elaborate, it gives the following error:

     Argument of type '{ [x: string]: string; }' is not assignable to parameter of type 'AddInventoryState | ((prevState: Readonly<AddInventoryState>, props: AddInventoryRowProps) => Add...'.

Type '{ [x: string]: string; }' is not assignable to type 'Pick

Removing the [] from name gives a similar error. Now I understand I could probably resolve this by simply specifying a type of any instead of AddInventoryState when creating my class, but this does not seem like a proper fix.

It is probably something simple, but how does one approach this in TypeScript?

Thanks.

2 Answers 2

2

This error is a bit hard to track but it is not due to the fact that the name does not exist in your state. It is due to the fact that your state properties are not marked as optional. Ergo:

export interface AddInventoryState {
    title?: string,
    ...
}

Because of this setState expects that you set all your properties like in the constructor. Luckily Mapped types have been added so you only need to mark the state as Partial like this for it to work:

export class AddInventoryRow extends React.Component<AddInventoryRowProps, Partial<AddInventoryState>>

Give the definitions for React 16.4,

// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
    state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
    callback?: () => void
): void;

You can also set state like this and dont have to mark AddInventoryState as Partial:

this.setState((current) => ({ ...current, [name]: value }))

More info:

https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types

https://reactjs.org/docs/forms.html#handling-multiple-inputs

allow typescript compiler to call setState on only one react state property

Sign up to request clarification or add additional context in comments.

Comments

0

this.setState( {[name]: value}); name doesn't exist in your state.

change it to this:

 this.setState(() => ({ mpn: value, }));

if that works, this.state.mpn should contain the value from your input.

1 Comment

Thanks for the response, but, unfortunately this still does not resolve my issue. I am trying to the alter the state variable based upon the value obtain from the currentTarget e.currentTarget. That is, using [name] in your code produces the same error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.