Extending react hook useState for use in event handlers I use React with material-ui.com and I love them both, but I'm tired with writing the boilerplate handlers like

onChange={(e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setEmail(e.currentTarget.value)}


again and again. Especially when stopPropagation is needed (AFAICT it's always either needed or harmless), it gets pretty verbose.

So I tried to extend useState so that it provides everything I need, so I can write things like:

const [email, setEmail, emailAgg] = useStateEx('');
...
onChange={emailAgg.changeHandler}


or

const [showPassword, , showPasswordAgg] = useStateEx(false);
...
onClick={showPasswordAgg.toggleHandler}


and similar.



My approach follows. I'd like it to be reviewed in general, especially for better typing (I had to use @ts-ignore) and improvements.

I case you hate semicolons, please ignore them as my eslint is set up to require them. ;)

import { useState, Dispatch, SetStateAction } from 'react';

function booleanAgg(value: boolean, setter: (value: boolean) =&gt; void, makeSetter: (value: boolean) =&gt; ((event?: any) =&gt; void)) {
    return {
        value,
        setter,
        falseHandler: makeSetter(false),
        trueHandler: makeSetter(true),
        toggleHandler: makeSetter(!value),
    };
}

function stringAgg(value: string, setter: (value: string) =&gt; void) {
    return {
        value,
        setter,
        changeHandler: (e: React.ChangeEvent&lt;HTMLInputElement|HTMLTextAreaElement&gt;) =&gt; {
            e.stopPropagation();
            setter(e.currentTarget.value);
        }
    };
}

function numberAgg(value: number, setter: (value: number) =&gt; void) {
    return {
        value,
        setter,
        changeHandler: (e: React.ChangeEvent&lt;HTMLInputElement|HTMLTextAreaElement&gt;) =&gt; {
            e.stopPropagation();
            setter(+e.currentTarget.value);
        }
    };
}

/**
 * Works like useState and adds a third element containing boolean-specific handlers directly useable in buttons and checkboxes.
 */
export function useStateEx(initialState: boolean) : [boolean, Dispatch&lt;SetStateAction&lt;boolean&gt;&gt;, ReturnType&lt;typeof booleanAgg&gt;];

/**
 * Works like useState and adds a third element containing string-specific handlers directly useable in text fields.
 */
export function useStateEx(initialState: string) : [string, Dispatch&lt;SetStateAction&lt;string&gt;&gt;, ReturnType&lt;typeof stringAgg&gt;];

/**
 * Works like useState and adds a third element containing number-specific handlers directly useable in text fields.
 */
export function useStateEx(initialState: number) : [number, Dispatch&lt;SetStateAction&lt;number&gt;&gt;, ReturnType&lt;typeof numberAgg&gt;];

/**
 * Prohibits use with any type not handled in the above overloads.
 */
export function useStateEx(initialState: any) : never;

export function useStateEx&lt;S extends boolean|string|number&gt;(initialState: S) : unknown {
    const [value, setter] = useState(initialState);
    function makeSetter(value: S) {
        return function(e: any) {
            if (typeof e?.stopPropagation === 'function') e.stopPropagation();
            setter(value);
        };
    }
    if (typeof value === 'boolean') {
        // @ts-ignore
        return [value, setter, booleanAgg(value, setter, makeSetter)]
    } else if (typeof value === 'string') {
        // @ts-ignore
        return [value, setter, stringAgg(value, setter)];
    } else if (typeof value === 'number') {
        // @ts-ignore
        return [value, setter, numberAgg(value, setter)];
    } else {
        throw new Error('Only boolean, string and number is supported');
    }
}
I must admit that my experience with typescript and react.js is quite limited so my review will be limited to basic syntax points. This code seems straight-forward. It makes good use of arrow functions and destruction assignment. There are just a couple suggestions I will make below.


if (typeof e?.stopPropagation === 'function') e.stopPropagation();



It is best to include brackets around the block, even if it is all on one line:

if (typeof e?.stopPropagation === 'function') { e.stopPropagation(); }


Some believe such blocks should never be on one line. If you are going to do it on one line, you could use short-circuiting:

typeof e?.stopPropagation === 'function' &amp;&amp; e.stopPropagation();




This block can be simplified somewhat:


if (typeof value === 'boolean') {
    // @ts-ignore
    return [value, setter, booleanAgg(value, setter, makeSetter)]
} else if (typeof value === 'string') {
    // @ts-ignore
    return [value, setter, stringAgg(value, setter)];
} else if (typeof value === 'number') {
    // @ts-ignore
    return [value, setter, numberAgg(value, setter)];
} else {
    throw new Error('Only boolean, string and number is supported');
}



The else keywords can be avoided because preceding blocks have return statements.'

if (typeof value === 'boolean') {
    // @ts-ignore
    return [value, setter, booleanAgg(value, setter, makeSetter)]
} 
if (typeof value === 'string') {
    // @ts-ignore
    return [value, setter, stringAgg(value, setter)];
} 
if (typeof value === 'number') {
    // @ts-ignore
    return [value, setter, numberAgg(value, setter)];
}
throw new Error('Only boolean, string and number is supported');