Reputation: 205
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
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
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