Viet
Viet

Reputation: 6953

React Hooks TypeScript event and state types

What are the types of React.js's state and event?

In my code below, I can only make it work by using type: any but it's just a hack. How can I use the right types for them?

In my custom hooks:

If I use function useFormFields(initialState: object), I get:

// value={inputs.item} error:
Property 'item' does not exist on type 'object'.ts(2339)
// onChange function error:
(JSX attribute) onChange: object
No overload matches this call.

If I use function(event: React.FormEvent) (which is true), I have this error:

Property 'id' does not exist on type 'EventTarget'.ts(2339)

If I use function(event: object), I have this error:

Property 'target' does not exist on type 'object'.ts(2339)

That's odd because below I use const handleSubmitItem = (event: React.FormEvent) and it works.

The answers I've found (like this one) don't work for me because Property 'id' does not exist on type 'EventTarget'

import React, {useState} from 'react';
import TextField from '@material-ui/core/TextField';
import IconButton from '@material-ui/core/IconButton';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';

/**
 * Custom hooks for input fields.
 * @param initialState initialState for Input Fields
 */
function useFormFields(initialState: any) {
    const [inputs, setValues] = useState(initialState);

    return [
        inputs,
        function(event: any) {
            setValues({
                ...inputs,
                [event.target.id]: event.target.value
            });
        }
    ];
}

export default function FormPropsTextFields() {
    const [inputs, handleInputChange] = useFormFields({
        item: '',
        quantity: '',
        store: ''
    });

    const handleSubmitItem = (event: React.FormEvent) => {
        event.preventDefault();
        console.log(inputs);
    };

    return (
        <form 
            className={classes.root} 
            noValidate autoComplete="off"
            onSubmit={handleSubmitItem}
        >
            <div>
                <TextField 
                    required id="item" 
                    label="Item" 
                    value={inputs.item}
                    onChange={handleInputChange}
                />
                <TextField
                    id="quantity"
                    label="Quantity"
                    type="number"
                    value={inputs.quantity}
                    onChange={handleInputChange}
                    InputLabelProps={{
                        shrink: true,
                    }}
                />
                <TextField 
                    id="store" 
                    label="Store" 
                    type="search"
                    value={inputs.store}
                    onChange={handleInputChange}
                />
                <IconButton 
                    type="submit"
                    color="primary" 
                    aria-label="add to shopping cart"
                >
                    <AddShoppingCartIcon />
                </IconButton>
            </div>
        </form>
    );
}

Upvotes: 1

Views: 5547

Answers (3)

nightElf
nightElf

Reputation: 522

I've made some corrections in the solution that you found. Hope it helps!

import React, {useState} from 'react';
import TextField from '@material-ui/core/TextField';
import IconButton from '@material-ui/core/IconButton';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';

/**
 * Custom hooks for input fields.
 * @param initialState initialState for Input Fields
 */

export interface MyModel {
    item: string
    quantity: string
    store: string
}

function useFormFields<T>(initialState: T): [T, (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void] {
    const [inputs, setValues] = useState<T>(initialState);

    return [
        inputs,
        function (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
            setValues({
                ...inputs,
                [event.target.id]: event.target.value
            });
        }
    ];
}

export default function FormPropsTextFields() {
    const [inputs, handleInputChange] = useFormFields<MyModel>({
        item: '',
        quantity: '',
        store: ''
    });

    const handleSubmitItem = (event: React.FormEvent) => {
        event.preventDefault();
        console.log(inputs);
    };

    return (
        <form 
            className={classes.root} 
            noValidate autoComplete="off"
            onSubmit={handleSubmitItem}
        >
            <div>
                <TextField 
                    required id="item" 
                    label="Item" 
                    value={inputs.item}
                    onChange={handleInputChange}
                />
                <TextField
                    id="quantity"
                    label="Quantity"
                    type="number"
                    value={inputs.quantity}
                    onChange={handleInputChange}
                    InputLabelProps={{
                        shrink: true,
                    }}
                />
                <TextField 
                    id="store" 
                    label="Store" 
                    type="search"
                    value={inputs.store}
                    onChange={handleInputChange}
                />
                <IconButton 
                    type="submit"
                    color="primary" 
                    aria-label="add to shopping cart"
                >
                    <AddShoppingCartIcon />
                </IconButton>
            </div>
        </form>
    );
}

Upvotes: 3

Ernesto
Ernesto

Reputation: 4272

Generals! the answer!

hope it helps!


import IconButton from '@material-ui/core/IconButton';
import TextField from '@material-ui/core/TextField';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import React, { useState } from 'react';

export interface MyModel {
    item: string;
    quantity: string;
    store: string;
 }

/**
 * Custom hooks for input fields.
 * @param initialState initialState for Input Fields
 **/
function useFormFields<T>(initialState: T) {
    const [inputs, setValues] = useState(initialState);

    return [
        inputs,
        function(event: React.FormEvent) {
            const {name, value} = event.currentTarget;
            setValues({
                ...inputs,
                [name]: value
            });
        }
    ];
}

export default function FormPropsTextFields() {
    const [inputs, handleInputChange] = useFormFields<MyModel>({
        item: '',
        quantity: '',
        store: '',
    });

    const handleSubmitItem = (event: React.MouseEvent<HTMLButtonElement>) => {
        event.preventDefault();
        /***
           make sure to have whatever attribute you want in the "html tag"
        */
        const { name, value } = event.currentTarget;
        console.log(inputs);
    };

    return (
        <form
            className={classes.root}
            noValidate autoComplete="off"
        >
            <div>
                <TextField
                    required id="item"
                    label="Item"
                    name="Item"
                    value={inputs.item}
                    onChange={handleInputChange}
                />
                <TextField
                    id="quantity"
                    label="Quantity"
                    name="Quantity"
                    type="number"
                    value={inputs.quantity}
                    onChange={handleInputChange}
                    InputLabelProps={{
                        shrink: true,
                    }}
                />
                <TextField
                    id="store"
                    label="Store"
                    name="Store"
                    type="search"
                    value={inputs.store}
                    onChange={handleInputChange}
                />
                <IconButton
                    type="button"
                    color="primary"
                    aria-label="add to shopping cart"
                    onClick={handleSubmitItem}
                >
                    <AddShoppingCartIcon />
                </IconButton>
            </div>
        </form>
    );
}

Upvotes: 1

messerbill
messerbill

Reputation: 5629

Since every component may be different you need to define the state and props types by yourself. There are some basic types defined for react (because every component may have children for example) but as i said, you will need to define your own types.

An example for a functional component:

const App: React.FC<{ message: string }> = ({ message }) => (
  <div>{message}</div>
);

The example above could also be written like so:

type MyType = { message: string }
const App: React.FC<MyType> = ({ message }) => (
  <div>{message}</div>
);

further reading here:

https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#section-2-getting-started

Upvotes: 1

Related Questions