1

I have some piece of code like below. I write custom hook with generic type for defined custom type return of my hook.

type Return<T> = [T, (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void, Record<keyof T, boolean>, () => void]


function useInput<T>(): Return<T> {
    const [input, setInput] = useState<T>({} as T);
    const [isDirty, setDirty] = useState({} as Record<keyof T, boolean>);

    const handleInputChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
        setInput({
            ...input,
            [e.target.name]: e.target.value
        });
        setDirty({
            ...isDirty,
            [e.target.name]: true
        });
    };

    const resetInput = () => {
        Object.keys(input).forEach((v) => input[v] = ''); //this line i get error
        setInput({...input});
    };


    return [input, handleInputChange, isDirty, resetInput]
}

export default useInput;

My generic type T is object. But when I loop over this, I get this error Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'. How can I defined type of key for generic type T? Please help me.

1 Answer 1

1

So Object.keys function is not generic parametrized, by this reason v in the forEach is always string. Here is reasoning why - Why doesn't Object.keys return a keyof type in TypeScript?.

Thats said you cannot narrow type of v it will be just string. What you can do is type assertion:

 Object.keys(input).forEach((v) => input[v as keyof T] = '')

But you will get next error as you don't define that T has all values as strings, and you assign to it empty string. This is actually correct error. You cannot assign to every field of unknown object a string.

In order to fix the second we need to narrow the type of value of T

function useInput<T extends Record<string, string>>(): Return<T>

We are saying now T has keys and values which are string or subtypes of string. Because of the fact that they can be subtypes of string we need to do the second assertion after. Why - Subtype can be for example 'a' | 'b' and such type cannot take empty string value.

Object.keys(input).forEach((v) => (input[v as keyof T] as string) = '')

But without stating that T extends Record<string, string> we would not able to assert property type of such to string.

As I said before such implementation has an issue, its risky as if you have object which is for example type A { a: 'x' | 'y'} and you pass it to such construct, then you can get empty string in property a and this is invalid member of type A. But if you work with types which have string values only, and I assume you do, then its fully ok.

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

Comments

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.