Search code examples
angularreactjstypescriptangular8angular-ivy

TypeScript Failed to compile because Type declaration of 'any' loses type-safety


How should I address the following error message:

Failed to compile /.../SoftwareLicenseCodes/index.tsx (14,20): Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type. This error occurred during the build time and cannot be dismissed.

See the following code:

import * as React from 'react';
import './SoftwareLicenseCodes.css';

interface SoftwareLicenseCodesProps {
}

interface SoftwareLicenseCodesState {
    count: string;
    oneTimeUsage: boolean;
    duration: string;
    validFrom: string;
    validTo: string;
    distributor: string;
    [key: string]: any;
}

class SoftwareLicenseCodes extends React.Component<SoftwareLicenseCodesProps, SoftwareLicenseCodesState> {
    constructor(props: SoftwareLicenseCodesProps) {
        super(props);

        this.state = {
            distributor: '',
            count:'',
            oneTimeUsage: false,
            duration: '',
            validFrom: '',
            validTo: ''
        };

        this.onInputChange = this.onInputChange.bind(this);
    }

    handleSubmit(event: React.FormEvent<HTMLFormElement>) {
        alert('submit');
        event.preventDefault();
    }

    onInputChange = (event: React.FormEvent<HTMLInputElement>) => {
        const value = event.currentTarget.type === 'checkbox' ? event.currentTarget.checked : event.currentTarget.value;

        this.setState({
            [name]: value
        });
    }

    render() {
        return (
            <div className="user-container software-codes">
                <div className="user-single-container">
                    <h1>Software License Codes</h1>

                    <form className="software-codes__form" onSubmit={this.handleSubmit}>
                        <label>
                            <span className="software-codes__input-element">Count</span>
                            <input
                                name="count"
                                type="number"
                                value={this.state.count}
                            />
                        </label>

                        <label>
                            <span className="software-codes__input-element">Distributor</span>
                            <input
                                name="distributor"
                                type="text"
                                value={this.state.distributor}
                            />
                        </label>

                        <label>
                            <span className="software-codes__input-element">One time usage</span>
                            <input
                                name="oneTimeUsage"
                                type="checkbox"
                                checked={this.state.oneTimeUsage}
                            />
                        </label>

                        <label>
                            <span className="software-codes__input-element">Duration</span>
                            <input
                                name="duration"
                                type="number"
                                value={this.state.duration}
                            />
                        </label>
                        <input className="software-codes__input-element" type="submit" value="Submit" />
                    </form>
                </div>
            </div>
        );
    }
}

export default SoftwareLicenseCodes;

Solution

  • Your code only sets either string or boolean values, so you could lock it down a bit more:

    interface SoftwareLicenseCodesState {
        count: string;
        oneTimeUsage: boolean;
        duration: string;
        validFrom: string;
        validTo: string;
        distributor: string;
        [key: string]: string|boolean;
        // ------------^^^^^^^^^^^^^^
    }
    

    Alternately, if you want to have full type safety, you could remove the string index signature and write extra code that switches on the name of the input and then uses an explicit property name. That maximizes your use of type-checking, while (obviously) increasing code size/complexity:

    function setNamed(target: SoftwareLicenseCodesState, name: string, value: string|boolean): SoftwareLicenseCodesState {
        if (name === "oneTimeUsage") {
            // Probably add assertion here that value is a boolean
            target.oneTimeUsage = value as boolean;
        } else {
            // Probably add assertion here that value is a string
            const strValue = value as string;
            switch (name) {
                case "count":
                    target.count = strValue;
                    break;
                case "duration":
                    target.duration = strValue;
                    break;
                case "validFrom":
                    target.validFrom = strValue;
                    break;
                case "validTo":
                    target.validTo = strValue;
                    break;
                case "distributor":
                    target.distributor = strValue;
                    break;
                default:
                    // Failed assertion here
            }
        }
        return target;
    }
    

    Then

    this.setState(setNamed({}, name, value));
    

    Clumsy as all get-out, but maximizes type-checking.

    I really want to find a way for you to use index types, but with the name coming from the name property of an input element, I can't see how to do that without the switch above. Which bothers me, because I seem to recall some uber-clever way of using keyof to build a union type for the name...