user9258981
user9258981

Reputation: 155

React Typescript createContext types issue

I am trying to pass todos (initial state) and addNewTodo (methods) using React Context hook and typescript. I have tried multiple solutions but no success, still getting errors.

Partial generics doesn't give issue on context component, but it gives me the error Cannot invoke an object which is possibly 'undefined' while calling addNewTodo in todo form component. Similarly, undefined and empty objects {} also give different errors. Can't figure out how to fix it. If I pass any then IntelliSense won't work.

Here is my code

TodoContext

import React, { useState, createContext, FC, useContext } from "react"

type Props = {
  children: React.ReactNode,
}

interface TaskContextProps {
  todos: Todo[],
  addNewTodo: addNewTodo
}

const initialTodos: Array<Todo> = [
  { id: 1, text: 'buy some milk', completed: false },
  { id: 2, text: 'go to gym', completed: false }
]

export const TaskListContext = createContext<Partial<TaskContextProps>>({})
// export const TaskListContext = createContext<TaskContextProps>({})
// export const TaskListContext = createContext<TaskContextProps | undefined>(undefined)

const TaskListContextProvider: FC<Props> = ({ children }) => {
  const [todos, setTodos] = useState(initialTodos)

  const addNewTodo: addNewTodo = (newTodo) => {
    setTodos([newTodo, ...todos])
  }

  return (
    <TaskListContext.Provider value={{ todos, addNewTodo }}>
      {children}
    </TaskListContext.Provider>
  )
}

Todo Form

import React, { useState, ChangeEvent, FormEvent, useContext } from 'react';

// import { useTaskList, TaskListContext } from '../context/TaskListContext';
import { TaskListContext } from '../context/TaskListContext';


const TodoForm = () => {
  const [newTodo, setNewTodo] = useState('')
  // const { addNewTodo } = useTaskList()
  const { addNewTodo } = useContext(TaskListContext)

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    setNewTodo(e.target.value)
  }

  const handleSubmit = (e: FormEvent<HTMLButtonElement>) => {
    e.preventDefault()
    const addTodo = { id: Math.random(), text: newTodo, completed: false }
    if (newTodo.trim()) {
      addNewTodo(addTodo)
    }
    else {
      alert('Todo can\'t be empty')
    }
    setNewTodo('')
  }

  return (
    <form>
      <input placeholder='your todo...' value={newTodo} onChange={handleChange} />
      <button onClick={handleSubmit}>Submit</button>
    </form>
  )
}

Your help will be appreciated.

Upvotes: 4

Views: 1628

Answers (1)

Alvaro
Alvaro

Reputation: 9662

To prevent TypeScript from telling that properties of an object are undefined we need to define them (using Partial sets every property as possibly undefined, which we want to avoid).

This means that we need to pass some value for each property when defining the context:

export const TaskListContext = createContext<TaskContextProps>({
    todos: [],
    addNewTodo: () => {}
});

This context initial values will be replaced as soon as the Provider is initialized, so they will never be read from a Component.

This way todos and addNewTodo will always have a value and TypeScript won't complain.

Upvotes: 6

Related Questions