Search code examples
reactjsreduxreact-reduxreact-functional-component

UseEffect with thunk in react-redux causing error (undefined property)


I'm learning react-redux-rails by building an application for a simple todo list. I'm finding myself getting increasingly confused with how certain parts of redux work and what it expects me to use. Currently, I'm trying to fetch a list of Todos from my DB and populate them as react components. I'm additionally trying to get accustomed to using functional components.

I have a single static page that renders a Provider with a store and App component

document.addEventListener('DOMContentLoaded', () => {
    const container = document.getElementById('root');
    const root = ReactDOMClient.createRoot(container)
    root.render(
        <Root store={store}/>
    )
})

Then I have a set of actions for Todos

export const RECEIVE_TODOS = "RECEIVE_TODOS"
export const RECEIVE_TODO = "RECEIVE_TODO"
export const REMOVE_TODO = "REMOVE_TODO"
import * as APIUtil from '../util/todo_api_util'

export const receiveTodos = (todos) => {
    return {
        type: RECEIVE_TODOS,
        todos,
    }
}

export const receiveTodo = (todo) => {
    return {
        type: RECEIVE_TODO,
        todo,
    }
}

export const removeTodo = (todo) => {
    return {
        type: REMOVE_TODO,
        todo,
    }
}

export const fetchTodos = () => dispatch => (
    APIUtil.fetchTodos().then(todos => dispatch(receiveTodos(todos)))
)

The last of these uses the thunk middleware (added to the store) and pulls from this simple API

export const fetchTodos = () => {
    return(jQuery.ajax({
        method: 'GET',
        url: '/api/todos',
    }))
}

Lastly, I have a TodoList Container

import { connect } from "react-redux";
import { receiveTodo } from "../../actions/todo_actions"
import TodoList from "./todo_list";
import { allTodos } from "../../reducers/selectors";
import { fetchTodos } from "../../util/todo_api_util";

const mapStateToProps = state => ({
    todos: allTodos(state)
});

const mapDispatchToProps = dispatch => ({
    receiveTodo: (todo) => dispatch(receiveTodo(todo)),
    requestTodos: () => dispatch(fetchTodos())
});

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(TodoList);

And the Todo List itself

import React, { useEffect } from "react";
import TodoForm from "./todo_form";
import TodoListItem from "./todo_list_item";

const TodoList = (props) => {
    console.log(props)

    useEffect(() => {
        props.requestTodos();
    })

    return(
        <div className="todo-list">
            <TodoForm 
                receiveTodo={ props.receiveTodo } />
            <ul className="todo-items">
                {props.todos.map((todo, idx) => {
                    return (
                        <TodoListItem key={idx} todo={todo} 
                        receiveTodo={ props.receiveTodo} />)
                })}
            </ul>
        </div>

    )
}

export default TodoList;

Now I thought that I understood useEffect for being a replacement of componentDidMount, but when I run this, I get the error as follows

react-redux error

I've tried reorganizing it in several ways (removing the '()', calling the store, etc), but can't get it to work. In addition, if I directly call store.dispatch(requestTodos()) in the console (with everything put onto the window), they will update the todos. Any ideas of what to do/why?


Solution

  • After researching functional components further, I needed to import { useDispatch } from react-redux to allow the store to update. I modified my Todo List component and it now works:

    
        import React, { useEffect, useState } from "react";
        import { useDispatch } from "react-redux";
        import TodoForm from "./todo_form";
        import TodoListItem from "./todo_list_item";
        
        const TodoList = (props) => {
        
            console.log(props)
        
            const dispatch = useDispatch();
        
            useEffect(() => {
                dispatch(fetchTodos())
            }, [])
        
            return(
                <div className="todo-list">
                    <TodoForm 
                        receiveTodo={ props.receiveTodo } />
                    <ul className="todo-items">
                        {props.todos.map((todo, idx) => {
                            return (
                                <TodoListItem key={idx} todo={todo} 
                                receiveTodo={ props.receiveTodo} />)
                        })}
                    </ul>
                </div>
        
            )
        }
        
        export default TodoList;