'How to declare the type for the input of a delete function in typescript?

I'm just starting out learning typescript and I am stuck trying to get types to match.

I have a delete function that will get rid of a todo item however that function needs to take in an id. In the beginning of my code I defined what a todo looks like which has an optional id property. It needs to be optional since the formdata that is added when a new todo is created does not have an id property and I can't give one since that is made on the backend. So I am stuck with having optional id properties but id needs to be a number.

This is the error:

TS2345: Argument of type '{ id?: number | undefined; name: string; description: string; }' is not assignable to parameter of type '{ id: number; }'.
  Types of property 'id' are incompatible.
    Type 'number | undefined' is not assignable to type 'number'.
      Type 'undefined' is not assignable to type 'number'.
    106 |                   <h2>{todo.name}</h2>
    107 |                   <p>{todo.description}</p>
  > 108 |                   <button onClick={() => deleteTodo(todo)}>Delete Todo</button>
        |                                                     ^^^^
    109 |                 </div>

Here is my code:

interface formState {
  id?: number
  name: string
  description: string
} 

const initialFormState = { name: '', description: '' } as formState

interface TodoState {
  todos: {
    // The question make means the type for that property is optional. 
    // It can either be a number or undefined
    id?: number
    name: string
    description: string
  }[]
  // the brackets above means that its an array
}

type Itodo = {
  id?: number
  name: string
  description: string
}

type getTodosQuery = {
  listTodos: {
    items: Itodo[]
    nextToken: string
  }
}


function App() {
  const [todos, setTodos] = useState<TodoState['todos']>([]);
  const [formData, setFormData] = useState(initialFormState);

  useEffect(() => {
    fetchTodos();
  }, []);

  async function fetchTodos() {
    try {
      const apiData = (await API.graphql({ query: listTodos })) as {
        data: getTodosQuery
      }
      // console.log("list of todos:", apiData.data.listTodos.items)
      setTodos(apiData.data.listTodos.items);
    } catch (error) {
      console.log(error)
    }
  }

  async function createTodo() {
    if (!formData.name || !formData.description) return;
    await API.graphql({ query: createTodoMutation, variables: { input: formData } });
    setTodos([ ...todos, formData ]);
    setFormData(initialFormState);
  }

  async function deleteTodo({ id }: {id: number}) {
    const newTodosArray = todos.filter(todo => todo.id !== id);
    setTodos(newTodosArray);
    await API.graphql({ query: deleteTodoMutation, variables: { input: { id } }});
  }

  return (
    <Authenticator>
      {({ signOut, user }) => (
        <div className="App">
          <h1>Todo Maker</h1>

          <input
            onChange={e => setFormData({ ...formData, 'name': e.target.value})}
            placeholder="Todo name"
            value={formData.name}
          />
          <input
            onChange={e => setFormData({ ...formData, 'description': e.target.value})}
            placeholder="Todo description"
            value={formData.description}
          />
          <button onClick={createTodo}>Create Todo</button>
          <div style={{marginBottom: 30}}>
            {
              todos.map(todo => (
                <div key={todo.id || todo.name}>
                  <h2>{todo.name}</h2>
                  <p>{todo.description}</p>
                  <button onClick={() => deleteTodo(todo)}>Delete Todo</button>
                </div>
              ))
            }
          </div>
          <button onClick={signOut}>Sign out</button>
        </div>
      )}
    </Authenticator>
  );
}

export default App;

How can I make todo have an id property be optional but let the delete function take in an id?



Solution 1:[1]

The simplest way to achieve what you want is to modify the code in the part where you output todos, like this:

            {
              todos.map(todo => todo.id && (
                <div key={todo.id}>
                  <h2>{todo.name}</h2>
                  <p>{todo.description}</p>
                  <button onClick={() => deleteTodo(todo)}>Delete Todo</button>
                </div>
              ))
            }

mind the todo.id && part.

Doing so will narrow the type of todo to the type which has id as mandatory field. So then you could use it safely.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kostiantyn Ko