0
votes

I am new to React + Redux and I am building a todo app.

Property 'todos' is missing in type '{}' but required in type '{ todos: Todo[]; }'. TS2741

I think the problem is I fail to connect (react-redux) state to TodoList. I can confirm this because I jump from <TodoList /> to its function component connect(mapStateToProps)(TodoList) rather than, supposedly, connect(mapStateToProps)(TodoList)

CodeSandbox: https://codesandbox.io/s/flamboyant-dream-w7drj?file=/src/App.tsx:712-713

I have read the react-redux doc but I did not find how to fix this.

Code:

import React, { useState } from 'react';
import { connect } from 'react-redux'
import { addTodo} from './index'

function AddToDo() {
  const [input, setInput] = useState('')

  function handleInput(e: React.ChangeEvent<HTMLInputElement>) {
    setInput(e.target.value)
  }

  //dispatch to store
  function handleAddTodo() {
    addTodo(input)
    setInput('')
  }

  return (
    <div>
      <input type="text" onChange={(e) => handleInput(e)}/>
      <button type="button" onClick={handleAddTodo}>
        Add todo
      </button>
    </div>
)
}

//connect
connect(null, { addTodo })(AddToDo)

//TodoList
interface Todo {
  id: number
  text: string
}

interface TodoState {
  todos: Todo[]
}

function TodoList({ todos}:{ todos: Todo[]}) {

  return (
    <ul className="todo-list">
      {todos.map((todo: Todo, index) => {
        return <Todo todo={todo} />;
        })
       }
    </ul>
  )
}

function mapStateToProps(state:TodoState) {
  return {state}
}

connect(mapStateToProps)(TodoList)

//Todo
function Todo({ todo }: { todo: Todo }) {

  return (
    <li>
      {todo}
    </li>
  )
}

function App() {
  return (
    <div className="App">
      <AddToDo />
      <TodoList /> <---------------------- Error info: Property 'todos' is missing in type '{}' but required in type '{ todos: Todo[]; }'.  TS2741
      </div>
  );
}

export default App;
2
Your mapStateToProps needs to return an object. Instead of return state.todos, change it to return {todos: state.todos}; - Dom
Thanks. I have fixed this issue (by return { state } ), but the error info persist. - Jill Clover
My mistake, your issue is with the typescript compiler erroring out? - Dom
The ts compiler returns error but I believe the root cause is I fail to connect state to the component. Added codesandbox link. - Jill Clover

2 Answers

1
votes

Based on your codesandbox example, I noticed a few issues.

TLDR - here's a working example using your code with some modifications.

Let's walk through it step-by-step going through each component.


index.tsx

const todoApp = combineReducers({
  todoReducer
});

should be changed to:

const todoApp = combineReducers({
  todos: todoReducer
});

Reason: your entire app is under the assumption that you access todos via state.todos. If you kept the code the way it originally was, you'd have to access it state.todoReducer.


AddToDo

function AddToDo() {
  const [input, setInput] = useState('')

  function handleInput(e: React.ChangeEvent<HTMLInputElement>) {
    setInput(e.target.value)
  }

  //dispatch to store
  function handleAddTodo() {
    addTodo(input)
    setInput('')
  }

  return (
    <div>
      <input type="text" onChange={(e) => handleInput(e)}/>
      <button type="button" onClick={handleAddTodo}>
        Add todo
      </button>
    </div>
)
}

should be changed to:

const addToDoConnector = connect(null, { addTodo });
// store the connect function so it can be used in typings and to create component
const ConnectedTodo = addToDoConnector(AddToDo);

function AddToDo(props: ConnectedProps<typeof addToDoConnector>) {
  const [input, setInput] = useState('')

  function handleInput(e: React.ChangeEvent<HTMLInputElement>) {
    setInput(e.target.value)
  }

  //dispatch to store
  function handleAddTodo() {
    props.addTodo(input)
    setInput('')
  }

  return (
    <div>
      <input type="text" onChange={(e) => handleInput(e)}/>
      <button type="button" onClick={handleAddTodo}>
        Add todo
      </button>
    </div>
)
}

Reason: You're importing a variable called addTodo at the top of the file. We need to access the AddToDo component's addTodo property, so it dispatches to the store.


TodoList:

function TodoList({ todos}:{ todos: Todo[]}) {

  return (
    <ul className="todo-list">
      {todos.map((todo: Todo, index) => {
        return <Todo todo={todo} />;
        })
       }
    </ul>
  )
}

function mapStateToProps(state:TodoState) {
  return { state }
}

connect(mapStateToProps)(TodoList)

should be changed to:

// store the connect function so it can be used in typings and to create component
const todoListConnector = connect(mapStateToProps);
const ConnectedTodoList = todoListConnector(TodoList);

function TodoList({todos}: ConnectedProps<typeof todoListConnector>) {
  return (
    <ul className="todo-list">
      {todos.map((todo: Todo, index) => {
        return <Todo todo={todo} />;
        })
       }
    </ul>
  )
}

function mapStateToProps(state:TodoState) {
  return state;
}

Reason: Your mapStateToProps was wrapping your state in a property called state. i.e. if your state is {todos: []}, it's changing it to {state: { todos: [] } }.


Todo

//Todo
function Todo({ todo }: { todo: Todo }) {

  return (
    <li>
      {todo}
    </li>
  )
}

should be changed to:

//Todo
function Todo({ todo }: { todo: Todo }) {
  return <li>{todo.text}</li>;
}

Reason:

Todo is an object. We'd want to render the object's text.


App

function App() {
  return (
    <div className="App">
      <AddToDo />
      <TodoList />
      </div>
  );
}

should be changed to:

function App() {
  return (
    <div className="App">
      <ConnectedTodo />
      <ConnectedTodoList />
      </div>
  );
}

Reason: Calling connect will not change the underlying component. You need to store that value in either a variable or have a dedicated file and export it.


Let me know if you have any other questions. Happy coding.

-1
votes

Check here working example with some modification CodeSandbox