Luka Reeson
Luka Reeson

Reputation: 123

React redux - pushing new item to the state array not working

I'm having an issue with adding a new item to the state array variable. In the "CategorySection" component I'm calling .push() to add "new_category" to the "categories" array, but nothing happens, not even an error.

As I try to create a new category by calling "handleAddCategory" which triggers a dispatch for "createCategory", I can see in the Redux DevTools that it has been added to the "item" state once I get the data back from my backend, but then when .push() is called in the "CategorySection" component, as I said before, nothing happens.

It's really weird, because I'm doing the same process in a different part of my project, and there it's working just fine. For example, I create a new Task, and when getting this created task back from my backend, I push it to the items state variable, and then the component gets re-rendered.

Category Reducer

const initialState = {
  items: [],
  item: {},
}

const reducer = (state=initialState, action) => {
  switch(action.type) {
    case GET_CATEGORIES:
      return {
        ...state,
        items: action.payload
      }

    case CREATE_CATEGORY:
      return {
        ...state,
        item: action.payload
      }
    

    default:
        return state;
  }
};

export default reducer;

Category Actions

export const getCategories = () => {
  return async(dispatch) => {
    const res = await fetch('/api/get-categories/', {
      method: 'GET',
      headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'Content-Type': 'application/json'
      }
    });
    const data = await res.json();
    dispatch({
      type: GET_CATEGORIES,
      payload: data
    });
  }
}

export const createCategory = (name) => {
  return async(dispatch) => {
    const res = await fetch('/api/manage-category/', {
      method: 'POST',
      headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'Content-type': 'application/json'
      },
      body: JSON.stringify({'name': name})
    });
    const data = await res.json();
    dispatch({
      type: CREATE_CATEGORY,
      payload: data
    });
  }
}

CategorySection component

Here I'm trying to call .push in the second useEffect, because sumTasks() must be called only when "categories" changes, but unfortunately it's not pushing at all...

import React from 'react'
import CategoryItem from './CategoryItem'
import AddPopover from '../popovers/AddPopover'
import { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { getTasks } from '../../state/action-creators/taskActions';
import { createCategory, setActiveCategory } from '../../state/action-creators/categoryActions';

const CategorySection = () => {
  const dispatch = useDispatch();

  const categories = useSelector((state) => state.category.items);
  const new_category = useSelector((state) => state.category.item);
  const active_category = useSelector((state) => state.category.active);

  const [anchorEl, setAnchorEl] = useState(null);
  const [totalTasks, setTotalTasks] = useState(0);
  const open = Boolean(anchorEl);

  useEffect(() => {
    sumTasks();
  }, [categories])

  useEffect(() => {
    categories.push(new_category);
    
  }, [new_category])

  function sumTasks() {
    let sum = categories.reduce((total, current) => total = total + current.total_tasks, 0);
    setTotalTasks(sum);
  }

  const onCategoryClick = async (id) => {
    if(active_category != id) {
      await dispatch(setActiveCategory(id));
      await dispatch(getTasks());
    }
  }

  const single_category = categories && categories.map((category) => {
    return(
      <CategoryItem key={category.id} category={category} active_category={active_category} onCategoryClick={onCategoryClick} />
    )
  })

  const handleClick = (event) => {
    setAnchorEl(event.currentTarget);
  };
  
  const handleClose = () => {
    setAnchorEl(null);
  };

  const handleAddCategory = async (e, value) => {
    if(e.keyCode === 13 && value != '') {
      setAnchorEl(null);
      await dispatch(createCategory(value));
    }
  }
  
  return (
    <div className="bg-dark mb-3" id="category-wrapper">
      <div className="container mt-2 mb-2">
        <div className="row">
          <span className="d-flex justify-content-between">
            <div className="fs-4 text-white">
              Categories
            </div>
            <div className="category-tooltip-div" data-bs-toggle="tooltip" data-bs-placement="right" title="">
              <svg onClick={handleClick} xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="orange" id="sidebar-add-category"
                className="bi bi-plus-square rename-add-icon category-add-icon global-add-category-icon" viewBox="0 0 16 16">
                <path
                  d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z" />
                <path
                  d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
              </svg>
              <AddPopover 
                open={open}
                anchorEl={anchorEl}
                horizontal={30}
                onClose={handleClose}
                placeholder="Enter Category name"
                handlePressEnter={handleAddCategory}
              />
            </div>
          </span>
        </div>
      </div>
      <div className="container" id="sidebar-categories">
        <li className={`row hovered-nav-item active-category ${active_category == -1 ? "item-selected" : ""}`}>
          <span onClick={() => onCategoryClick(-1)} className="category-link category-item fs-5 d-flex " id="sidebar-all-tasks" value="-1">
            <div className="all-tasks-text">All</div>

            {totalTasks > 0 ? (
              <div className="total-number">
                <div className="number">{totalTasks}</div>
              </div>
            ):''}

          </span>
        </li>
        <div id="categories-loop">
          {single_category}
        </div>
      </div>
    </div>
  )
}

export default CategorySection

Upvotes: 1

Views: 1675

Answers (1)

Luka Reeson
Luka Reeson

Reputation: 123

I've changed the CREATE_CATEGORY reducer to:

case CREATE_CATEGORY:
      return {
        ...state,
        items: [...state.items, action.payload]
      }

Upvotes: 3

Related Questions