I have a callback function, that is supposed to edit a value in an array, that is inside of a State. When I try to access the dataZähler
, so the array itself, I get unedefined/Array of length 0 back. Why is that?
Code in Question(hopefully reduced to its bare necessities):
'use client'
import { useCallback, useEffect, useState } from "react";
import { ProtokollEintrag, TogglePosition, ZählerCallback, ZählerObjekt } from "./dataTypes";
import { ToggleButton, ToggleButtonGroup, Divider, ZoomProps } from "@mui/material";
import ElectricalServicesIcon from '@mui/icons-material/ElectricalServices';
import clsx from "clsx";
import { Modal } from "react-bootstrap";
import { CreateCounterPopUp, CreateEntryPopUp, EditCounterPopUp, EditEntryPopUp, CalculateDeltaPopUp } from "./pages/myPopups";
export default function Home() {
//data and internal logic states, table-content, which rows are selected, is the data still loading, etc.
const [loading, setLoading] = useState(true);
const [dataZähler, setDataZähler] = useState<ZählerObjekt[]>([]);
const [dataProtokoll, setDataProtokoll] = useState<ProtokollEintrag[]>([]);
const [selectedRowZähler, setSelectedRowZähler] = useState(-1);
const [selectedRowProtokoll, setSelectedRowProtokoll] = useState(-1);
//View-Logic states, which table is shown, if modals should be seen, etc
const [showEditZählerModal, setShowEditZählerModal] = useState(false);
//on start, load both csv's into states
useEffect(() => {
fetch('/api/loadCsv')
.then((res) => res.json())
.then((data) => {
setDataZähler(data.dataZähler)
setDataProtokoll(data.DataProtokoll)
setLoading(false)
})
}, []);
function handleEditZähler() {
if (currentTable === TogglePosition.Zähler && selectedRowZähler >= 0) {
setShowEditZählerModal(true);
}
};
const handleEditZählerCallback: ZählerCallback = useCallback((data: ZählerObjekt | null) => {
console.log(dataZähler);
if (data) {
const oldData = dataZähler.map((elem, idx) => {
if (idx === selectedRowZähler)
return { ...data };
return elem;
});
console.log(data);
console.log(oldData);
setDataZähler(oldData);
setSelectedRowZähler(-1);
}
setShowEditZählerModal(false);
}, []);
return (
<div className="flex flex-col">
{/* Buttons und Toggle zur Verwendung und Steuerung des Interfaces */}
<div className="justify-center flex sticky bottom-0 space-x-4 bg-[white] bg-opacity-60">
<button onClick={handleEditZähler} disabled={selectedRowZähler < 0} className={clsx("bg-[#0ea5e9] border-[none] text-[white] text-center inline-block text-base rounded-[5px] px-5 py-3 my-2", { "bg-[#d1d5db]": selectedRowZähler < 0 })}>Bearbeiten</button>
</div>
{/*Modal Div - all edit/Cancel/Save/Create/Delete modals go here*/}
<div>
<Modal size="lg" show={showEditZählerModal} onHide={() => setShowEditZählerModal(false)}>
<Modal.Header closeButton>
<Modal.Title>
Zähler Bearbeiten:
</Modal.Title>
</Modal.Header>
<Modal.Body>
<EditCounterPopUp data={dataZähler[selectedRowZähler]} callback={handleEditZählerCallback} />
</Modal.Body>
</Modal>
</div>
</div>
);
}
and if it is important, here is the Modal for Editing the ZählerObjekt:
import { ChangeEvent, FormEvent, useState } from "react";
import { ZählerObjekt, ProtokollEintrag, ZählerCallback, EintragCallback } from "../dataTypes";
import { Form } from "react-bootstrap";
import { generateGenericObjectID } from "../util/objectIdentifier";
interface CounterProps {
data: ZählerObjekt,
callback: ZählerCallback
}
export function EditCounterPopUp({ data, callback }: CounterProps) {
const [newCounter, setNewCounter] = useState({ ...data });
function handleSpeichern(event: React.MouseEvent<HTMLButtonElement>) { //dont forget to set the id of the new object
event.preventDefault();
setNewCounter({ ...newCounter, ...{ id: generateGenericObjectID(newCounter) } });
callback(newCounter);
}
function handleInputChange(field: string, event: ChangeEvent<HTMLInputElement>) {
const newState = { ...newCounter };
newState[field as keyof ZählerObjekt] = event.target.value;
setNewCounter(newState);
}
return (
<div>
<form>
{Object.entries(newCounter).slice(1).map((elem, idx) => {
return (
<Form.Group
className="mb-3" key={idx} controlId={elem[0]}
>
<Form.Label>{elem[0]}</Form.Label>
<Form.Control
as="textarea"
rows={2}
placeholder={"z.B. " + String(data[elem[0] as keyof ZählerObjekt])}
value={elem[1]}
onChange={(e) => handleInputChange(elem[0], e as any)} />
</Form.Group>
);
})}
<div className="float-right">
<button onClick={handleSpeichern} className="bg-[#22c55e] border-[none] text-[white] text-center inline-block text-base rounded-[5px] px-5 py-3 my-2">Speichern</button>
</div>
</form>
</div>
);
}
Why is dataZähler
unedfined in the callback? and how can I fix this?
The issue is an empty dependency array. Include dataZähler
and selectedRowZähler
in the dependency array of useCallback
. Check the code below:
const handleEditZählerCallback: ZählerCallback = useCallback((data: ZählerObjekt | null) => {
if (data) {
const oldData = dataZähler.map((elem, idx) => {
if (idx === selectedRowZähler)
return { ...data };
return elem;
});
setDataZähler(oldData);
setSelectedRowZähler(-1);
}
setShowEditZählerModal(false);
}, [dataZähler, selectedRowZähler]);
The dependencies array [dataZähler, selectedRowZähler] ensures that the callback has the most recent values of these dependencies every time they change. Without this, the callback may use stale values of dataZähler and selectedRowZähler.