2
votes

I'm trying to do the redux basic usage tutorial which uses react for the UI.

I'm getting this warning (in red though - perhaps it is an error?) in the console logs when I click a button labelled "Add Todo":

warning.js:36 Warning: Failed prop type: The prop todos[0].id is marked as required in TodoList, but its value is undefined. in TodoList (created by Connect(TodoList)) in Connect(TodoList) (at App.js:9) in div (at App.js:7) in App (at index.js:12) in Provider (at index.js:11)

So the todo that is getting added, has no id field - I need to figure out how to add an id.

in actions.js

/*
 * action creators
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

actions/index.js

let nextTodoId = 0
export const addTodo = (text) => {
  return {
    type: 'ADD_TODO',
    id: nextTodoId++,
    text
  }
}

export const setVisibilityFilter = (filter) => {
  return {
    type: 'SET_VISIBILITY_FILTER',
    filter
  }
}

export const toggleTodo = (id) => {
  return {
    type: 'TOGGLE_TODO',
    id
  }
}

containers/AddTodo.js

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => {
          input = node
        }} />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo

It looks like actions/index.js does add the todo id?

It's hard to debug because for some reason the chrome dev tools sources are missing the actions and reducers folders of my app:

enter image description here

enter image description here

How do I get the todos[0] to have a unique id?

note that when I add id: 1 here it does get the id added but it is not unique:

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false,
          id: 1
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

Maybe:

/*
 * action creators
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

needs id added?

2

2 Answers

6
votes

I briefly looked over your code, and I believe you're not getting that id because you're not importing the action creator function you think you are.

in containers/AddTodo.js:

import { addTodo } from '../actions'

In your project, you have

./src/actions.js
./src/actions/index.js

When you import or require anything (without using file extensions like the above '../actions'), the JS interpreter will look to see if there's a file called actions.js in the src folder. If there is none, it will then see if there's an actions folder with an index.js file within it.

Since you have both, your AddTodo component is importing using the action creator in ./src/actions.js, which does not have an id property as you had originally guessed.

Remove that file, and it should work as you intended.

0
votes

You have to add an 'id' variable to the actions file then increase the value every time you call the action creator.

action creator:

let nextTodoId = 0;

export const addTodo = text => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text
});

reducer:

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id, // unique id
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      )
    default:
      return state
  }
}