i got a problem while working with mui data-grid and socket.io. I load initial data from an api which works fine. But when I want to update the table with data from an websocket is only one element added and overwritten on every change. The code is the following:
Table:
const [tableData, setTableData] = useState(props.tableData);
useEffect(() => {
setTableData(props.tableData);
}, [props.tableData]);
return (<div key="tradingTableGrid" style={{ height: 450, width: "100%" }}>
<DataGrid
key={"TradingTable"}
rows={Object.values(tableData)}
columns={tableHead}
rowHeight={30}
pageSize={10}
onRowClick={(row) => openModal(row)}
/>
</div>);
The component where the initial data are loaded and the websocket update is called is the following (i have only added the essential code):
const [data, setData] = useState({});
const [loadData, setLoadData] = useState(false);
const [socketActive, setSocketActive] = useState(false);
// initial data loading from api
const handleResponse = (response) => {
var result = {};
response.forEach((prop) => {
result = {
...result,
[prop.order_id]: {
id: prop.order_id,
orderId: prop.order_id,
price: prop.price.toString(),
volume: prop.max_amount_currency_to_trade,
minVolume: prop.min_amount_currency_to_trade,
buyVolume: prop.max_volume_currency_to_pay + " €",
minBuyVolume: prop.min_volume_currency_to_pay + " €",
user: prop.trading_partner_information.username,
},
};
});
setData(result);
};
// add from websocket
const addOrder = (order) => {
if (
order.trading_pair === "btceur" &&
order.order_type === "buy" &&
(order.payment_option === "1" || order.payment_option === "3")
) {
if (typeof data[order.order_id] === "undefined") {
console.log(data);
setData({
...data,
[order.order_id]: {
id: order.order_id,
orderId: order.order_id,
price: order.price.toString(),
volume: order.amount,
minVolume: order.min_amount,
buyVolume: Number(order.volume).toFixed(2) + " €",
minBuyVolume: Number(order.min_amount * order.price).toFixed(2) + " €",
user: "not set through socket",
},
});
}
}
};
useEffect(() => {
const loadData = () => {
setLoadData(true);
axios
.get(process.env.REACT_APP_BITCOIN_SERVICE + "/order/book?type=buy")
.then((response) => handleResponse(response.data.orders))
.catch((exception) => console.error(exception))
.finally(() => setLoadData(false));
};
if (Object.keys(data).length === 0 && socketActive === false) {
loadData();
}
const socket = () => {
const websocket = io("https://ws-mig.bitcoin.de:443/market", {
path: "/socket.io/1",
});
websocket.on("connect", function () {
setSocketActive(true);
});
websocket.on("remove_order", function (order) {
removeOrder(order);
});
websocket.on("add_order", function (order) {
addOrder(order);
});
websocket.on("disconnect", function () {
setSocketActive(false);
});
};
if (socketActive === false && Object.keys(data).length !== 0) {
socket();
}
}, [data, socketActive]);
return (
<Table
tableHeaderColor="info"
tableHead={tableHead}
tableData={data}
type="buy"
/>
);
Does anyone has an idea why on the update throught the socket only one element in the data is added and on every next call overwritten?
The reason is that the socket handlers are using the state setters from the first render.
as an example take addOrder
, you create that function within the context of a specific data value, when you call setData({...data
it will always use the initial state for data. (to understand why see How do JavaScript closures work? and also A Complete Guide to useEffect from facebook's react developer Dan Abramov)
what you can do instead is use the functional update syntax that lets you pass a function to setData:
setData( data => ({...data,
this ensures the last stored value is always used.
see the react docs on useState Functional updates