envincebal
envincebal

Reputation: 205

I can edit certain inputs but not others in React/Redux Toolkit

I'm building a task manager app using Redux Toolkit to manage state. The activeBoard property in the initialState of the board slice. When I import that into the Edit page, I'm able to edit the boardName property in the input but not any of text in the columns array within the activeBoard object. I keep getting an error

"Cannot assign to read only property 'board' of object

The columns array from the activeBoard object are passed into the "columns" useState hook. The strange thing is when I hardcode an array onto the useState hook, I'm able to edit each columns input. Any help would be appreciated.

EditBoardModal.jsx

import React, {useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {editBoard} from "../../reducers/board/boardSlice";
import {hideModal} from "../../reducers/modal/modalSlice";
import Button from "../Button/Button";
import "./EditBoardModal.scss";

const EditBoardModal = () => {
  const dispatch = useDispatch();
  const {activeBoard} =  useSelector(store => store.board);
  const [boardName,
    setBoardName] = useState(activeBoard.name);
  const [columns,
    setColumns] = useState([...activeBoard.columns]);

  const nameChangeHandler = (e) => {
    setBoardName(e.target.value)
  }

  const columnsChangeHandler = (i, e) => {
    let columnsValues = [...columns];
    columnsValues[i][e.target.name] = e.target.value;
    console.log(columnsValues[0][e.target.name]);
    setColumns(columnsValues);
  }


  return (
    <div className="edit-board-modal">
      <h3 className="edit-modal-title">Edit Board</h3>
      <div className="edit-board-name-div">
        <label>Board Name</label>
        <input
          value={boardName}
          onChange={nameChangeHandler}
          className="edit-task-title"
          type="text"
          name="edit board name"
          placeholder="e.g. Web Design"/>
      </div>
      <div className="edit-board-columns-div">
        <label>Board Columns</label>
        {columns.map((column, index) => (
          <div className="edit-columns-item-div" key={index}>
            <input
             onChange={e => columnsChangeHandler(index, e)}
              className="edit-column-input"
              type="text"
              name="board"
              value={column.board}
              placeholder="e.g. Web Design"/>
            <svg onClick={() => deleteColumn(index)} key={index} width="15" height="15" xmlns="http://www.w3.org/2000/svg">
              <g fill="#828FA3" fillRule="evenodd"><path d="m12.728 0 2.122 2.122L2.122 14.85 0 12.728z"/><path d="M0 2.122 2.122 0 14.85 12.728l-2.122 2.122z"/></g>
            </svg>
          </div>
        ))}
      </div>

      <Button
        onClick={() => dispatch(hideModal())}
        text={"+ Add New Column"}
        className={"add-column-subtask"}/>
      <Button
        onClick={() => {
          dispatch(hideModal())
          dispatch(editBoard(boardName));
        }}
        text={"Save Changes"}
        className={"create-save-changes"}/>

    </div>
  )
}

export default EditBoardModal

boardSlice.js

import {createSlice} from "@reduxjs/toolkit";

const boardSlice = createSlice({
  name: "board",
  initialState: {
    boards: [],
    activeBoard: null
  },
  reducers: {
    setActiveBoard: (state, {payload}) => {
      const getBoardByID = state.boards.find(el => el.id === payload);
      state.activeBoard = getBoardByID;
    },
    addBoard: (state, {payload}) => {
      const newBoard = {
        id: payload.id,
        name: payload.name,
        columns: payload.columns
      }
      if (state.boards.length === 0) {
        state.activeBoard = newBoard;
      }
      state.boards.push(newBoard);
    },
    editBoard: (state, {payload}) => {
      state.activeBoard.name = payload;
    },
    deleteCurrentBoard: (state, {payload}) => {
      const deletedBoard = state.boards.filter(item => item.id !== state.activeBoard.id);

      state.boards = deletedBoard;

      state.activeBoard = state.boards[0];
    },
    addColumn: (state, {payload}) => {
      state.activeBoard.columns.push(payload)
    }

  }
});

export const {
  addBoard,
  setActiveBoard,
  deleteCurrentBoard,
  addColumn,
  editBoard
} = boardSlice.actions;

export default boardSlice.reducer;

Upvotes: 2

Views: 554

Answers (2)

Drew Reese
Drew Reese

Reputation: 202618

The columns state is a shallow copy of the activeBoard.columns array, but the elements are all still references to the original array held in the slice.

const [columns, setColumns] = useState([...activeBoard.columns]);

The columnsChangeHandler function also creates a shallow copy of the columns array but again, the array elements are still original references to the original array in the slice. columnsChangeHandler is attempting to mutate these references.

const columnsChangeHandler = (i, e) => {
  let columnsValues = [...columns]; // <-- copy of array, but not elements
  columnsValues[i][e.target.name] = e.target.value; // <-- mutate element
  setColumns(columnsValues);
}

When updating React state you must shallow copy all state (and nested state) that is being updated. Use Array.prototype.map to shallow copy the array and for the matching index create a new object reference and shallow copy the element and override the appropriate property.

Example:

const columnsChangeHandler = (i, e) => {
  const { name, value } = e.target;
  setColumns(columns => columns.map((el, index) => index === i
    ? {
      ...el,
      [name]: value,
    }
    : el
  ));
}

Upvotes: 1

alexsander hamir
alexsander hamir

Reputation: 1

This error usually occurs when you try to change a property of an object that has been frozen or when a property has been defined with Object.

click here for more information: https://bobbyhadz.com/blog/javascript-cannot-assign-to-read-only-property-of-object

Upvotes: 0

Related Questions