React 中按键时组件不会重绘

问题描述 投票:0回答:1

我将一组组件作为子组件添加到父组件中。每个子组件都有许多变量并包含在 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>&nbsp;
                            <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}&deg;</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>

)
});


https://codesandbox.io/p/sandbox/xenodochial-pine-t6ccl8

javascript reactjs
1个回答
0
投票
  1. 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]);
  1. 使用
    state
    存储组件的类似问题可能会导致过时的关闭 link1 link2

解决方案:不要

const [deskComponents, setDeskComponents] = useState(null);

直接使用此功能

<React.Fragment>{buildDesks()}</React.Fragment>
  1. 避免重新创建初始状态。在你的情况下,它是
    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

© www.soinside.com 2019 - 2024. All rights reserved.