我将一组组件作为子组件添加到父组件中。每个子组件都有许多变量并包含在 JSON 中。当我使用箭头键时,我可以更新其中一个孩子(桌子)的位置,但在刷新之前,该更新不会绘制在页面上。
如何强制每次按键更新?我相信问题是,因为我正在使用对象,所以每个对象的地址不会改变,并且不会触发重绘。
我尝试在状态下使用
Math.random()
强制重画,但没有成功。
如何修改它以允许在每次按键时进行重绘?
/* eslint-disable no-use-before-define */
import React, { useState, useRef, useContext, useEffect, useCallback } from 'react';
import { useQuery } from 'react-query';
import useKeypress from 'react-use-keypress';
import { Loading } from '../Elements/LoadingComponent';
import { Row, Col } from 'react-bootstrap';
import AddDesks from '../Elements/Layout/AddDesks';
import Status from '../Elements/Layout/Status';
import { Desk } from '../Elements/Layout/Desks';
import { SiteMapContext } from '../../App';
import {
Layout_Get_Sites,
Layout_Get_Desk_Types,
Layout_Update_Desk_Data,
Layout_Create_Desk,
Layout_Set_UserImage,
} from '../../network/Layout_Creator';
import '../../shared/styles/layout.css';
const LayoutMap = (props) => {
const mapRef = useRef();
const mapContainer = useRef();
const desksRef = useRef([]);
const siteMap = useContext(SiteMapContext);
let roles = siteMap.siteMapData.user.roles;
const isAdmin = roles.indexOf('admin') + roles.indexOf('system') > 0
let siteCount = 0;
const [mapScale, setMapScale] = useState();
const [unused, rerender] = useState(Math.random());
const [desks, setDesks] = useState({ past: [], present: [], future: [] });
const [currentDesk, setDesk] = useState({ index: -1, desk: null });
const [currentMap, setCurrentMap] = useState(-1);
const [deskTypes, setDeskTypes] = useState(-1);
const [maps, setMaps] = useState({ maps: [] });
const [editMode, setEditMode] = useState(false);
//const [deskComponents, setDeskComponents] = useState(null)
const [mapImage, setMapImage] = useState(`data:image/jpeg;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==`);
const { isLoading: areMapsLoading, refetch } = useQuery('LayoutMaps', () => Layout_Get_Sites(siteCount),
{
staleTime: Infinity,
refetchInterval: false,
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
onSuccess: (data) => {
let map = data.maps;
if (map) {
let newMaps = [...maps.maps, map];
setMaps({ maps: newMaps })
siteCount = newMaps.length;
}
if (!data.done) refetch();
},
})
useEffect(() => {
const loadDeskTypes = async () => {
let dt = await Layout_Get_Desk_Types();
setDeskTypes(dt);
}
window.addEventListener('resize', updateSize);
loadDeskTypes();
return () => {
window.removeEventListener('resize', updateSize);
}
}, [])
useEffect(() => {
var img = `data:image/jpeg;base64,${maps.maps[currentMap]?.SiteBackground ?? 'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='}`
setMapImage(img);
}, [currentMap])
useEffect(() => {
setDesks({ ...desks, present: maps.maps[currentMap]?.desks });
var img = `data:image/jpeg;base64,${maps.maps[currentMap]?.SiteBackground ?? 'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='}`
setMapImage(img);
}, [editMode])
useEffect(() => {
setDesks({ ...desks, present: maps.maps[currentMap]?.desks });
}, [mapScale]);
const updateSize = () => {
//TODO Not implemented yet
}
//called when location Select is used
const changeMap = target => {
console.log("Layout.changeMap");
setDesk({ index: -1, desk: null });
const map = parseInt(target.value);
setCurrentMap(map);
setEditMode(false);
}
const clickMap = () => {
clearCurrent();
setDesk({ index: -1, desk: null });
}
const createDesk = async (newDesk) => {
Layout_Create_Desk(newDesk)
.then(data => {
let newDesks = data.desks;
setDesks({ past: desks.present, present: newDesks, future: [] });
});
}
const updateDesk = (desk) => {
let id = desk.id;
let index = desks.present.findIndex(d => d.id === id);
let presentDesks = desks.present;
let newDesks = presentDesks;
newDesks[index] = desk;
setDesks({ past: presentDesks, present: newDesks, future: [] });
setDesk({ index: index, desk: desk })
Layout_Update_Desk_Data(desk);
}
const setUserImage = (file) => {
Layout_Set_UserImage(file, currentDesk)
}
const clearCurrent = () => {
for (let i = 0; i < desksRef.current.length; i++) {
if (desksRef.current[i] && desksRef.current[i].img().style) {
desksRef.current[i].img().style.border = "none";
}
};
}
const setCurrentDesk = (index) => {
clearCurrent();
let desk = desksRef.current[index];
desk.img().style.border = '2px solid red';
setDesk({ index: index, desk: desk });
}
const buildMapOption = (site, index) => {
var ret = <option value={index} key={site.id}>{site.SiteName}</option>
return ret;
}
const buildMapSelector = () => {
var ret =
<select onChange={(e) => changeMap(e.target)} value={currentMap}>
<option id='-1'>Select Site...</option>
{
maps.maps.map((site, index) => buildMapOption(site, index))
}
</select>
return ret;
}
const getScale = () => {
const site = maps.maps[currentMap] //the id on the select
if (!site) return;
let map = mapRef.current;
let rect = map?.getBoundingClientRect();
let mapWidth = rect.width;
let mapHeight = rect.height;
let scaleW = (mapWidth && site?.wMM) ? (mapWidth / site.wMM) : 1;
let scaleH = (mapHeight && site?.hMM) ? (mapHeight / site.hMM) : 1;
setMapScale({ height: scaleH, width: scaleW });
}
const buildDesk = (desk, index) => {
const res =
<Desk key={desk.id}
desk={desk}
isEditing={editMode}
desks={desks.present}
scale={mapScale}
deskTypes={deskTypes}
currentDesk={currentDesk.index}
fns={DeskFns}
ref={el => desksRef.current[index] = el}
index={index}
rdm={Math.random()}
/>
return res;
}
useKeypress(['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown'], (e) => {
//const keyDown = useCallback((e) => {
e.preventDefault();
if (currentDesk.index > -1 && editMode === true) {
var key = e.key;
const desk = desks.present[currentDesk.index];
switch (key) {
case 'ArrowLeft': //left
desk.x = Math.round(desk.x - (5 / mapScale.width));
break;
case 'ArrowUp': //up
desk.y = Math.round(desk.y - (5 / mapScale.height));
break;
case 'ArrowRight': //right
desk.x = Math.round(desk.x + (5 / mapScale.width));
break;
case 'ArrowDown'://down
desk.y = Math.round(desk.y + (5 / mapScale.height));
break;
default: break;
}
updateDesk(desk);
desksRef.current[currentDesk.index].redraw(desk.x, desk.y);
rerender(Math.random())
}
}, [currentDesk, editMode]);
const showStatus = () => {
return (currentDesk.index > -1) ? <Status currentDesk={currentDesk} desks={desks.present} />
: null
}
const AddDesksFns = {
Update_Desklist: setDesks,
Create_Desk: createDesk
}
const DeskFns = {
Update_Desklist: setDesks,
Update_Desk_Data: updateDesk,
Set_UserImage: setUserImage,
Set_Current: setCurrentDesk,
Get_Current: () => currentDesk > -1 ? desks.present[currentDesk].id : -1 ,
//redraw: redraw,
}
//onKeyDown = { keyDown }
// Press_Key: keyDown,
return (
<div>
<Row>
<Col sm={1}>
{(isAdmin && !areMapsLoading) ?
<span style={{ 'whiteSpace': 'nowrap', 'backgroundColor': '#ccc', 'visibility': `${currentMap !== -1 ? 'visible' : 'hidden'}` }}>
<label htmlFor='layoutEditSelect'>Edit</label>
<input id='layoutEditSelect' type='checkbox'
onClick={async (e) => { setEditMode(e.target.checked); }}
/>
</span>
: <span></span>
}
</Col>
<Col sm={10}>
{(editMode && currentDesk != null) ?
<div style={{ position: 'absolute', top: '100px', left: '300px' }}>
<Row>
<Col>Left</Col><Col>{Math.round(currentDesk.x)}</Col>
<Col>Height</Col><Col>{currentDesk.height}</Col>
</Row>
<Row>
<Col>Top</Col><Col>{Math.round(currentDesk.y)}</Col>
<Col>Width</Col><Col>{currentDesk.width}</Col>
</Row>
<Row>
<Col>Rotation</Col>
<Col>{currentDesk.rotation}°</Col>
</Row>
</div>
: ''
}
</Col>
</Row>
<Row>
<Col sm={1}>
<Row>
<Col>
{!areMapsLoading ?
buildMapSelector()
:
<Loading title="Map Data" />
}
</Col>
<Col>
<AddDesks
maps={maps}
deskTypes={deskTypes}
editMode={editMode}
scale={mapScale}
fns={AddDesksFns}
/>
</Col>
</Row>
</Col>
<Col sm={10} id="Layout_Map_Container" ref={mapContainer} >
{/* <BuildMap map={maps.maps[props.maps.current]} fns={props.MapFunctions} scale={props.scale} /> */}
{/*onClick={clickDesk}*/}
<div ref={mapContainer}
style={{ width: '100%', height: `${window.height}px`, position: 'relative' }}
onClick={() => clickMap()}
onLoad={() => { getScale(); clickMap(); } /*If map is reset, set current desk to null*/}
className='map'
id="Layout_SiteMap_Img"
>
{/**/}
<img src={mapImage}
style={{ width: '100%', position: 'absolute' }}
alt={`Layout plan for ${maps.maps[currentMap]?.currentSite?.SiteName}`}
ref={mapRef}
id="floorMap"
onLoad={getScale}
/>
<React.Fragment>
{//deskComponents
mapScale? maps.maps[currentMap]?.desks.map((desk, index) => buildDesk(desk, index)):''
}
</React.Fragment>
</div>
</Col>
</Row >
{showStatus()}
</div>
)
}
export default LayoutMap;
}
import React, { forwardRef, useState, useImperativeHandle, useRef, useEffect } from 'react';
import { useQuery } from 'react-query';
import { Row, Col } from 'react-bootstrap';
import { Droppable } from '../Droppable';
import ContentEditable from 'react-contenteditable';
import Draggable from 'react-draggable';
import {
Layout_Get_Desk_Types,
} from '../../../network/Layout_Creator';
export const Desk = forwardRef(({ desk, isEditing, scale, deskTypes, fns, index }, ref) => {
const imgRef = useRef(null);
const [rotation, setRotation] = useState(desk?.rotation ?? 0);
const [top, setTop] = useState(0);
const [left, setLeft] = useState(0);
const [unused, rerender] = useState(Math.random())
const [size, setSize] = useState(() => {
let deskImg = null;
var dImg = deskTypes?.find(dsk => dsk.deskType === desk.deskType);
//desk top * scale gives top in MM, multiply by map scale
let top = parseInt(desk.y * scale.height);
const left = parseInt(desk.x * scale.width);
let width = 0;
let height = 0;
try {
if (dImg) {
deskImg = dImg.deskImage;
width = parseInt(dImg.wMM * scale.width);
height = parseInt(dImg.hMM * scale.height);
}
let imgStyle = {
boxSizing: "border-box",
width: `${width}px`,
height: `${height}px`,
}
//position: 'absolute'
const url = `data:image/jpeg;base64,${deskImg}`;
setTop(top ?? 0);
setLeft(left ?? 0);
return { url: url, style: imgStyle };
}
catch (ex) {
console.log(ex);
}
//return base coords if full data not available
return { left: left, top: top };
});
const handleImageClick = (e) => {
e.stopPropagation();
fns.Set_Current(index);
};
const handleImageDoubleClick = () => {
if (isEditing) {
setRotation((prevRotation) => {
let newRotation = prevRotation + 90
fns.Update_Desk_Data({ ...desk, rotation: newRotation })
return newRotation;
});
}
};
const handleDrag = (e, ui) => {
// Handle drag functionality
const { x, y } = ui;
//console.log("Moving ", ui)
// Update image position
};
const handleDragComplete = (e, { lastX, lastY }) => {
//convert back to a MM basis
let newX = Math.round(lastX / scale.width);
let newY = Math.round(lastY / scale.height);
fns.Update_Desk_Data({ ...desk, x: newX, y: newY })
/*
deltaX: 0
deltaY: 0
lastX: 444
lastY: 206
9 x:444
y: 206
*/
}
//const handleKey = (e) => {
// e.preventDefault()
// fns.Press_Key(e, index); //}
useImperativeHandle(ref, () => ({
redraw: (x, y) => {
setLeft(Math.round(x * scale.width));
setTop(Math.round(y * scale.height));
rerender(Math.random())
},
img: () => imgRef.current
}));
return (
<Draggable
key={desk.id} // **Force re-render when desk.id changes**
defaultPosition={{ x: left, y: top }}
onDrag={handleDrag}
onStop={handleDragComplete}
disabled={!isEditing} // Ensure editing state is passed correctly
>
<div >
<img ref={imgRef}
alt={`{DeskID:${desk.id} }`}
src={size.url}
style={{
...size.style,
position: 'absolute',
transform: `rotate(${rotation}deg)`,
cursor: 'move',
}}
onClick={(e) => handleImageClick(e)} // Ensure clicks are handled
onDoubleClick={handleImageDoubleClick} // Ensure double clicks are handled
/>
</div>
</Draggable>
)
});
keyDown
事件处理程序取决于desks
和currentDesk
等状态,但您仅在组件安装时注册它,这意味着提到的那些依赖项不会在函数范围内更新。解决方案:将事件注册移动到具有这些依赖项的另一个
useEffect
中
useEffect(() => {
console.log("add event");
window.addEventListener("keydown", keyDown);
return () => {
//window.removeEventListener("resize", updateSize);
window.removeEventListener("keydown", keyDown);
};
}, [currentDesk, desks, editMode]);
解决方案:不要
const [deskComponents, setDeskComponents] = useState(null);
直接使用此功能
<React.Fragment>{buildDesks()}</React.Fragment>
size
中的
desks.js
解决方案:切换到
useMemo
const size = useMemo(() => {
//logic here
}, [desk]);
数学和拖放功能也有问题,但就这个问题的范围而言,我不会碰它们。
这里正在工作分叉的codesandbox:https://codesandbox.io/p/sandbox/cranky-driscoll-4wpxc6?file=%2Fsrc%2FDesks.js%3A41%2C33