Реактивное обновление состояния после выполнения кода, требующего изменения состояния - PullRequest
0 голосов
/ 06 января 2020

Я пытаюсь создать сетку узлов, которые обновляются при нажатии / перетаскивании по ним. Я сталкиваюсь со странным React State, где состояние обновляется после кода, следующего за ним.

Ожидаемое поведение:

  • nodeTypePointer установлен в 0 в качестве состояния реакции по умолчанию
  • Нажмите на пустой узел, nodeTypePointer установлен в 0 (без изменений) + logi c использует 0
  • Нажмите на цветной узел, для nodeTypePointer установлено значение 1 + logi c используется 1

Фактическое поведение

  • для nodeTypePointer установлено значение 0 в качестве стандартного состояния реакции
  • Нажмите на пустой узел, nodeTypePointer остается неизменным + logi c использует 0
  • Нажмите на цветной узел в первый раз , установлен nodeTypePointer 0 (без изменений) + logi c использует 0, затем после завершения кода для nodeTypePointer установлено значение 1
  • Нажмите на цветной узел во второй раз , nodeTypePointer теперь по-прежнему установлен в 1, logi c использует 1, затем после завершения кода nodeTypePointer установлен в 0

This can be seen on this screenshot

Я знаю, что состояние асин c, но не уверен в точной причине / проблеме / решении до go вкл. Не уверен, как передать параметры следующим функциям, если бы я использовал UseEffect.

Вот код:

import React, { useEffect, useState } from 'react';
import Node from './Node'

import './Grid.css';

const rectDiameter = 25
const gridHeightRatio = 0.9

const Grid: React.FC = () => {

    const gridHeight: number = Math.floor(window.innerHeight * gridHeightRatio)
    const gridWidth: number = window.innerWidth

    const numX: number = Math.floor(gridHeight / rectDiameter)
    const numY: number = Math.floor(gridWidth / rectDiameter)

    const tempGrid: number[][] = [...Array(numX)].map(() => Array(numY).fill(0));

    const [grid, setGrid] = useState(tempGrid)
    const [isMousePressed, setMousePressed] = useState(false)
    const [nodeTypePointer, setNodeTypePointer] = useState(0)

    useEffect(() => {
        console.log('Use Effect', nodeTypePointer)
    }, [nodeTypePointer])

    // Hacky el.ID to workaround React performance until I know how to do it better
    const paintNode = (x: number, y: number) => {
        const el = document.getElementById(`${x}-${y}`)
        if (!el) return
        if (nodeTypePointer === 0) {
            el.classList.add("node-wall")
        } else {
            el.classList.remove("node-wall")
        }
    }

    const updateGridPosition = (x: number, y: number) => {
        const newValue = nodeTypePointer === 0 ? 1 : 0
        let newGrid: number[][] = grid
        newGrid[x][y] = newValue
        setGrid(newGrid)
    }

    const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>, x: number, y: number) => {
        console.log('Node pointer type Before', nodeTypePointer)
        setNodeTypePointer(grid[x][y])
        console.log('Node pointer type After', nodeTypePointer)
        setMousePressed(true)
        console.log('Before updated Grid', grid[x][y])
        updateGridPosition(x, y)
        console.log('After updated Grid', grid[x][y])
        paintNode(x, y)                             // <------------- Uses nodeTypePointer
    }

    const handleMouseUp = (event: React.MouseEvent<HTMLDivElement>, x: number, y: number) => {}

    const handleHover = (event: React.MouseEvent<HTMLDivElement>, x: number, y: number) => {
        if (!isMousePressed) return
        updateGridPosition(x, y)
        paintNode(x, y)
}

    return (
        <div className='gridContainer' >
            {
                grid.map((row: number[], i: number) =>
                    row.map((val: number, j: number) =>
                        <Node
                            x={i}
                            y={j}
                            d={rectDiameter}
                            key={`${i}-${j}`}
                            state={val}
                            onMouseDown={handleMouseDown}
                            onMouseUp={handleMouseUp}
                            onHover={handleHover} />
                    )
                )
            }
        </div>
    );
}

export default Grid

Любая помощь в понимании проблемы и предлагаемых решений будет очень полезна оценили!

1 Ответ

0 голосов
/ 06 января 2020

Изменения состояния асинхронны (как компонентные, так и подключаемые). Таким образом, ваше состояние не обновляется к тому времени, когда вы звоните paintNode. Есть два способа обдумать это. Один из них заключается в том, чтобы поместить вызов paintNode в ваши nodeTypePointer useEffect, например, так:

 useEffect(() => {
     paintNode(x,y);
 }, [nodeTypePointer])

, но это означает, что вам нужно сохранить x и y of clicked один в некоторых стандартных переменных, так как вы не сможете передать их непосредственно в useEffect. Другой способ (и, возможно, лучший, так как я не большой поклонник изменений состояния, имеющих побочные эффекты) - просто передать x / y прямо в paintNode во время функции handleMouseDown:

paintNode(x, y, grid[x][y])

однако, похоже, что updateGridPosition также требуется х / у, так что, возможно, вам будет лучше сохранить исходное нажатое в состоянии, затем иметь useEffect для нажатого, которое делает все, что нужно после нажатия на узел. Все зависит от того, как будет выглядеть эта функция.

Но да, причина вашей ошибки в том, что изменения состояния асин c, поэтому она не обновляется к тому времени, когда вы вызываете paintNode

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...