1

Am working on a todo app using react redux, its initial state is an object initialState and has an array task which is an empty array. When setting the values of the task, I can't seem to find a way to get the current index of the objects to assign it to id. I have tried using the indexOf() and the findIndex() but haven't come across the best way to approach it. I want the id to be equal to its object's index. (If object is at index 0 in the task array, that id to be equal to 0) And to also work when the array is changed or has more than one object. I appreciate the help, thanks

This is my initial state

export const initialState = {
    task: []
}

This is my reducer

const manageTodoReducer = (state, action) => {
    switch(action.type) {
        case "ADD_TODO":
            return (
                {
                    ...state,
                    task: [
                        ...state.task, 
                        {
                            name: "This is the name", 
                            id: state.task.findIndex(x => x.name === state.task.name), 
                            complete: false
                    }]
                }
            )

            default:
                return state
            }
        }
Steen Toons
  • 69
  • 1
  • 9
  • In case the array is empty you can push the object to the array and set the id as 0. Does this work for your case? – Sarun UK Jun 11 '21 at 13:28
  • Yes the array is initially empty, but when the id is set to 0, every id in all the object gets fixed to 0. In my case, I wanted the id to change dynamically according to the change in index, in that, even when I delete elements inside the array, all id change according to the new array. – Steen Toons Jun 12 '21 at 07:11

1 Answers1

1

You could use state.tasks.length but that would break when you have 2 tasks, delete the first and then add a task (see example, you will end with 2 tasks having id of 1).

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;

const initialState = {
  tasks: [{ id: 0 }, { id: 1 }],
};
//action types
const ADD_TASK = 'ADD_TASK';
const REMOVE_TASK = 'REMOVE_TASK';
//action creators
const addTask = () => ({
  type: ADD_TASK,
});
const removeTask = (id) => ({
  type: REMOVE_TASK,
  payload: id,
});
const reducer = (state, { type, payload }) => {
  if (type === ADD_TASK) {
    return {
      ...state,
      tasks: state.tasks.concat({ id: state.tasks.length }),
    };
  }
  if (type === REMOVE_TASK) {
    return {
      ...state,
      tasks: state.tasks.filter(({ id }) => id !== payload),
    };
  }
  return state;
};
//selectors
const selectTasks = (state) => state.tasks;
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      () => (next) => (action) => next(action)
    )
  )
);
const App = () => {
  const tasks = useSelector(selectTasks);
  const dispatch = useDispatch();
  const remove = (id) => dispatch(removeTask(id));
  const add = () => dispatch(addTask());
  return (
    <div>
      <button onClick={add}>Add task</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <pre>{JSON.stringify(task)}</pre>
            <button onClick={() => remove(task.id)}>
              remove
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>

<div id="root"></div>

It is not adviced to use index unless you absolutely have to and never do it when you re order or remove items from the list.

I would advice making a function that generates ids:

//in case you use redux persist and tasks will persist between reloads
const genId = (state) =>
  Math.max(...state.map(({ id }) => id).concat(-1)) + 1;    

//reducer
id: genId(state.tasks),

See working example below

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;

const initialState = {
  tasks: [{ id: 0 }, { id: 1 }],
};
//action types
const ADD_TASK = 'ADD_TASK';
const REMOVE_TASK = 'REMOVE_TASK';
//action creators
const addTask = () => ({
  type: ADD_TASK,
});
const removeTask = (id) => ({
  type: REMOVE_TASK,
  payload: id,
});
//in case you use redux persist and tasks will persist between reloads
const genId = (state) =>
  Math.max(...state.map(({ id }) => id).concat(-1)) + 1;
const reducer = (state, { type, payload }) => {
  if (type === ADD_TASK) {
    return {
      ...state,
      tasks: state.tasks.concat({
        id: genId(state.tasks),
      }),
    };
  }
  if (type === REMOVE_TASK) {
    return {
      ...state,
      tasks: state.tasks.filter(({ id }) => id !== payload),
    };
  }
  return state;
};
//selectors
const selectTasks = (state) => state.tasks;
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      () => (next) => (action) => next(action)
    )
  )
);
const App = () => {
  const tasks = useSelector(selectTasks);
  const dispatch = useDispatch();
  const remove = (id) => dispatch(removeTask(id));
  const add = () => dispatch(addTask());
  return (
    <div>
      <button onClick={add}>Add task</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <pre>{JSON.stringify(task)}</pre>
            <button onClick={() => remove(task.id)}>
              remove
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>

<div id="root"></div>
HMR
  • 37,593
  • 24
  • 91
  • 160
  • 1
    This is an awesome approach, also the code snippet provided helped me understand more in react redux. Thanks – Steen Toons Jun 11 '21 at 16:10
  • 1
    I used the `genId()` function to set the `task.id` and set the `task.id` as the new id for the component. When clicked, I passed the id to the `action.payload` and used it to filter the array thus deleting items. It worked! Thank you very much – Steen Toons Jun 14 '21 at 10:17