react and typescript useState on a object

18,301

Solution 1

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 })}
     />
    )
}

Solution 2

You want to spread into a new object and then override the properties you need e.g.

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

Although, given you are setting just product you might want to not making it a nested property of the state and just making it the state itself.

Share:
18,301

Related videos on Youtube

Robolisk
Author by

Robolisk

Student

Updated on May 29, 2022

Comments

  • Robolisk
    Robolisk almost 2 years

    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).

  • Robolisk
    Robolisk about 4 years
    This was an excellent solution, I wasn't aware you can do such a thing with useState which will seem to fix a few areas within my application. In doing this react throws me a new warning: 'Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). ...'
  • wentjun
    wentjun about 4 years
    @Robolisk ahh..! I am guessing because name, product, and stock are all undefined due to them being optional properties. This can be fixed by short-circuiting them with a default value if the properties are undefined. Let me update my answer.
  • wentjun
    wentjun about 4 years
    In addition, you might need to use optional chaining (such as product?.price), as they might be undefined..
  • Robolisk
    Robolisk about 4 years
    ah yes that solved the problem. I'm migrating from a class react background to hooks + typescript, lots of learning curves here. Thanks for the quick and informed solution.
  • wentjun
    wentjun about 4 years
    @Robolisk Glad to help! you can try this too. This should work: value={product?.name??''}