1

Interface

  interface ListInterface {
    listName: string;
    list: string[];
  }

useState

  const [initialList, setinitialList] = useState<ListInterface>({listName: "List1", list:['One', 'Two', 'Three', 'Four', 'Five', 'Six']});
  const [selectedList, setSelectedList] = useState<ListInterface>(initialList);

I know how to add new elements (push) to array. But having trouble when the array is in object/inteface. How do you do it with useState hooks?

setTheArray(oldArray => [...oldArray, newElement]);

I have created a simple sandbox example.

  • Use the input field to add a new text to array selectedList.list

https://codesandbox.io/s/reacr-ts-nested-object-array-example-z1uyd?file=/src/App.tsx

skyboyer
  • 22,209
  • 7
  • 57
  • 64
CookieMonster
  • 241
  • 1
  • 9
  • 26

2 Answers2

3

You have defined an object interface, so that means that you need to push an object that conforms to that type each time you want to update it.

  
  // ...

  const [text, setText] = useState<string>(''); // <--- set type and set to blank to remove type errors due to `text` being possibly undefined

  // ...

  const handleSubmit = (event: any) => {    
    event.preventDefault();
    console.log("Submitted: " + text);

    //setSelectedList({list: oldArray => [...oldArray, newElement]});
    // the above wont work because it is an array, but `ListInterface` is an object (you might want to change the name to `ListEntry` or `ListObject` to avoid confusion)

    setSelectedList({
      listName: selectedList.listName,
      list: [...selectedList.list, text]
    })

  };

Appreciate the accept. Check out @axtck's answer as well, as they make a few other important improvements to your code!

Harley Lang
  • 2,063
  • 2
  • 10
  • 26
1

You shouldn't initialize a state variable for your initial object, you could just use the interface and declare it that way:

const initialList: ListInterface = {
  listName: "List1",
  list: ["One", "Two", "Three", "Four", "Five", "Six"]
};

Since you only want to update the list inside the object, you can use functional setState() like this:

const handleSubmit = (event: any) => {
  console.log("Submitted: " + text);
  event.preventDefault();

  setSelectedList((prevState) => {
    return { ...prevState, list: prevState.list = [...prevState.list, text] };
  });
};

When mapping, make sure you pas a valid key:

{selectedList.list.map((item, i) => {
  return <h3 key={i}>{item}</h3>;
})}

Full code & updated sandbox:

import { useState } from "react";
import "./styles.css";

export default function App() {
  interface ListInterface {
    listName: string;
    list: string[];
  }

  const initialList: ListInterface = {
    listName: "List1",
    list: ["One", "Two", "Three", "Four", "Five", "Six"]
  };

  const [selectedList, setSelectedList] = useState<ListInterface>(initialList);
  const [text, setText] = useState("");

  const handleChange = (event: any) => {
    setText(event.target.value);
  };

  const handleSubmit = (event: any) => {
    console.log("Submitted: " + text);
    event.preventDefault();

    setSelectedList((prevState) => {
      return { ...prevState, list: prevState.list = [...prevState.list, text] };
    });
  };

  return (
    <div className="App">
      {selectedList.list.map((item, i) => {
        return <h3 key={i}>{item}</h3>;
      })}

      <form onSubmit={handleSubmit}>
        <label>
          Add To List:
          <input type="text" value={text} onChange={handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    </div>
  );
}
axtck
  • 3,707
  • 2
  • 10
  • 26