reactjstypescriptreact-hooksuse-reducer

Array is not iterable in useReducer()


To practice React hooks I wrote a simple todo site with ReactTS. I faced a problem that I keep receiving an error that my todos array is not iterable, despite it is defined as TodoProps[] in State type

Error occurs logically after 'add' button click

Error: state.todos is not iterable

If I write console.log(state.todos) I receive undefined

I checked code on medium and it seems to be identical to mine, but anyway I cannot resolve this issue.

(Error place marked in code)

TodoProps:

export interface TodoProps {
      title: string
      id: number
      todoKey?: number
      completed: boolean
}

LayoutContext.tsx:

import { createContext, useState, ReactNode } from 'react'

export const TodoListContext = createContext<any>({})
export const TitleContext = createContext<any>({})

type LayoutContextProps = {
      children: ReactNode
}

export const LayoutContext = ({ children }: LayoutContextProps): JSX.Element => {
      const [title, setTitle] = useState<string>('')
      const [todoList, setTodoList] = useState<object[]>([
            {title: 'Todo 1', id: 418759},
            {title: 'Todo 2', id: 440123}
      ])

      return (
            <TitleContext.Provider value={{ title, setTitle }}>
                  <TodoListContext.Provider value={{ todoList, setTodoList }}>
                        {children}
                  </TodoListContext.Provider>
            </TitleContext.Provider>
      )
}

Layout.tsx:

import { Button, Todos, Input } from 'Components'
import { LayoutContext } from 'Context/LayoutContext'
import cn from 'classnames'
import styles from './Layout.module.scss'

export const Layout = (): JSX.Element => {
      return (
            <section className={cn(styles.layout)}>
                  <LayoutContext>
                        <Todos/>
                        <Input/>
                        <Button>Add</Button>
                  </LayoutContext>
            </section>
      )
}

Button.tsx:

import styles from './Button.module.scss'
import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode, useReducer, useContext, useEffect } from 'react';
import { TodoListContext, TitleContext } from '../../Context/LayoutContext';
import { TodoProps } from 'Components/TodoComponent/Todo';

interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
      children: ReactNode
}

export const Button = ({ children }: ButtonProps): JSX.Element => {
      const { todoList } = useContext(TodoListContext)
      const { title, setTitle } = useContext(TitleContext)

      type State = { todos: TodoProps[] }

      type Action = { type: 'addTodo', payload: string }

      const reducer = (state: State, action: Action) => {
            switch (action.type) {
                  case 'addTodo':
                        const newTodoCard: TodoProps = { 
                              title: action.payload,
                              id: Math.floor(Math.random() % 10),
                              completed: false
                        }

                        console.log(todoList)
                        console.log(state.todos)

                        return { 
                                     Error here
                                     \/\/\/\/\/\/\/\/\/\/\/\/\/\/
                              todos: [...state.todos, newTodoCard]
                                     /\/\/\/\/\/\/\/\/\/\/\/\/\/\ 
                        }
            }
      }

      const [state, dispatch] = useReducer(reducer, todoList)

      const handleAdd = (e: any) => {
            e.preventDefault()

            dispatch({ type: 'addTodo', payload: title})
            setTitle('')
      }

      return (
            <button
                  type='submit'
                  onClick={handleAdd}
                  className={styles.button}
            >{children}</button>
      )
}

Don't know if I can add any more information..


Solution

  • You are passing an array to the initial state into your reducer and accessing it as an object in your reducer so you just need to change these lines

    from

    const [state, dispatch] = useReducer(reducer, todoList)
    

    To

    const [state, dispatch] = useReducer(reducer, { todos: todoList });
    

    and you are good to go.