Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react and typescript useState on a object

I have a type product within a React component:

type TProduct = {
  name: string,
  price: string,
  stock: string
}

in which my component I wish to change the values through a input box:

const AddProductCard: React.SFC = () => {
  const classes = useStyles();
  const [product, setProduct] = React.useState({ product:{} as TProduct})
  return (
     <input 
      onChange={e => setProduct({...product ,product: {name: e.target.value }})}
     />
     <input 
      onChange={e => setProduct({...product ,product: {stock: e.target.value }})}
     />
     <input 
      onChange={e => setProduct({...product ,product: {price: e.target.value }})}
     />
    )
  }

I thought by adding ...product it would copy over all the same values within product and just change the value I wanted to but that didn't seem to work. It would set a new product object although it would just rewrite the whole content of the object with the only the most recent entered input.

I then attempted a different approach with an interface where:

interface IProduct {
  name: string;
  price:  string;
  stock: string;
}

and

const [product, setProduct] = React.useState<IProduct | undefined>(undefined);

and using a similar state change as I have shown previously, but alas that only occurred in another new bug (I guess I'm not sure how to properly use an interface in react).

like image 759
Robolisk Avatar asked Feb 07 '20 08:02

Robolisk


1 Answers

Apart from supplying the methods for the onChange events on the inputs, you will need to specify the value as well. For instance,

value={product.name}

In addition, I am not sure why is the product state deeply nested, as there is no need to go one level deeper in this scenario, This will reduce the complexity when you need to update the product state on each of the onChange events. You only need to spread the current product state, followed by updating the property with the new values. For instance,

setProduct({
  ...product, 
  name: e.target.value 
});

In addition, when it comes to typing the product state, there is no need to assert is as TProduct. Given that the properties may be undefined, you can keep the following interface,

interface TProduct {
  name: string;
  price:  string;
  stock: string;
}

And set the typings for product as Partial<TProduct>.

Last but not least, if you are working with the newer versions of React, it will be sufficient to type your component as React.FC.

This is how your component should look like.

const AddProductCard: React.FC = () => {
  const classes = useStyles();
  const [product, setProduct] = React.useState<Partial<TProduct>>({});

  return (
     <input 
      value={product.name || ''}
      onChange={e => setProduct({...product, name: e.target.value })}
     />
     <input 
      value={product.stock || ''}
      onChange={e => setProduct({...product, stock: e.target.value })}
     />
     <input 
      value={product.price || ''}
      onChange={e => setProduct({...product, price: e.target.value })}
     />
    )
}
like image 89
wentjun Avatar answered Oct 14 '22 09:10

wentjun