Переменная React useState сбрасывается в значение True, когда я обращаюсь к ней

#javascript #reactjs #canvas

Вопрос:

У меня очень странная проблема, когда моя логическая переменная useState (allowClickUpload) имеет значение True, когда я уже установил для нее значение False. Возможно, вы думаете, что я получаю доступ к нему слишком рано, и это асинхронная операция, но я могу подождать до 10 полных минут, а затем запустить журнал консоли, и это всегда верно.

В приведенном ниже коде я регистрирую любые изменения в консоли, чтобы разрешить загрузку с помощью эффекта использования. Результат такой, как и ожидалось… Сначала верно, потому что это мое начальное значение, а затем, как только я загружаю файл, оно становится ложным. Пока все хорошо. Затем я еще раз нажимаю на экран и выводю значение, и это правда, без срабатывания эффекта использования. Как это возможно? Как будто существует более 1 экземпляра или что-то в этом роде. Я действительно понятия не имею.

Пожалуйста, ознакомьтесь с приведенным ниже кодом. Это область холста, которая позволяет пользователям перетаскивать на нее изображение. Я удалил весь «лишний» код, чтобы оставить только голую реализацию, которая все еще вызывает проблему. Любая помощь будет признательна.

 
import React, { useState, useEffect } from "react";
import styled from "styled-components";

const CanvasPanel = () => {
  const [allowClickUpload, setAllowClickUpload] = useState(true);
  const [img, setImg] = useState(document.createElement("img"));
  const [data, setData] = useState(null);
  const [context, setContext] = useState({});
  const [canvas, setCanvas] = useState({});

  useEffect(() => {
    // LOG every change to allowClickUpload
    console.log("allowClickUpload "   allowClickUpload);
  }, [allowClickUpload]);

  const clearCanvas = () => {
    context.clearRect(0, 0, canvas.width, canvas.height);
  };

  const centerImage = () => {
    var windowWidth = canvas.width;
    var windowHeight = canvas.height;
    var imgWidth = img.width;
    var imgHeight = img.height;

    var left, top;

    if (imgWidth >= windowWidth) {
      left = 0;
    } else {
      left = (windowWidth - imgWidth) / 2;
    }

    if (imgHeight >= windowHeight) {
      top = 0;
    } else {
      top = (windowHeight - imgHeight) / 2;
    }

    context.drawImage(img, left, top, imgWidth, imgHeight);
    setData(context.getImageData(left, top, imgWidth, imgHeight).data);
  };

  const processDrop = (file) => {
    if (file.type === "image/png" || file.type === "image/jpeg") {
      var reader = new FileReader();
      reader.onload = function (evt) {
        img.src = evt.target.resu<
      };

      reader.readAsDataURL(file);

      setAllowClickUpload(false);
    }
  };

  const handleStopEvent = (e) => {
    e.preventDefault();
    e.stopPropagation();
  };

  const handleDrop = (e) => {
    e.preventDefault();
    e.stopPropagation();

    if (e.dataTransfer.files amp;amp; e.dataTransfer.files.length > 0) {
      processDrop(e.dataTransfer.files[0]);
      e.dataTransfer.clearData();
    }
  };

  const handleImageLoad = () => {
    clearCanvas();
    centerImage();
  };

  const handlePickFile = (e) => {
    processDrop(e.target.files[0]);
  };

  const handleMouseDown = () => {
    // allowClickUpload is always True here even though the useEffect shows it is false
    console.log("allowClickUpload "   allowClickUpload);
    if (allowClickUpload) {
      document.getElementById("fileUpload").click();
    }
  };

  useEffect(() => {
    var canvas = document.getElementById("draganddrop-wrapper");
    const { width, height } = canvas.getBoundingClientRect();
    canvas.width = width;
    canvas.height = height;

    canvas.addEventListener("dragenter", handleStopEvent);
    canvas.addEventListener("dragleave", handleStopEvent);
    canvas.addEventListener("dragover", handleStopEvent);
    canvas.addEventListener("drop", handleDrop);
    canvas.addEventListener("mousedown", handleMouseDown);

    window.addEventListener("drop", handleStopEvent);
    window.addEventListener("dragover", handleStopEvent);

    img.addEventListener("load", handleImageLoad);

    setCanvas(canvas);
    setContext(canvas.getContext("2d"));

    return () => {
      canvas.removeEventListener("dragenter", handleStopEvent);
      canvas.removeEventListener("dragleave", handleStopEvent);
      canvas.removeEventListener("dragover", handleStopEvent);
      canvas.removeEventListener("drop", handleDrop);
      canvas.removeEventListener("mousedown", handleMouseDown);

      window.removeEventListener("drop", handleStopEvent);
      window.removeEventListener("dragover", handleStopEvent);

      img.removeEventListener("load", handleImageLoad);
    };
  }, [context]);

  return (
    <Wrapper>
      <canvas id="draganddrop-wrapper"></canvas>
      <input
        type="file"
        className="hidden"
        id="fileUpload"
        onChange={(e) => handlePickFile(e)}
      />
    </Wrapper>
  );
};

const Wrapper = styled.div`
  height: calc(100vh - 160px);
  flex: 1;
  background: var(--neutral-color);
  border: 1px solid var(--dark-text-color);
  color: var(--dark-text-color);
  margin: 0;
  canvas {
    width: calc(100vw - 310px);
    height: calc(100vh - 160px);
    image-rendering: pixelated;
  }
  .hidden {
    display: none;
  }
`;

export default CanvasPanel;


 

Вот вывод с моей консоли при запуске. Первый вывод-результат использования, показывающий значение true при загрузке. Далее-ложь, потому что я на самом деле устанавливаю ее в значение false. Далее верно, что исходит от handleMouseDown(). Также комментарии в коде.

вывод console_output

Ответ №1:

Вы добавили контекст только в качестве зависимости к своему крючку useEffect, который настраивает прослушиватели событий. Несмотря на то, что ваш компонент выполняет повторную передачу и функция обновляется, прослушиватель событий все еще ссылается на исходную функцию, в которой ваше состояние было истинным.

Комментарии:

1. Это хороший момент, могу ли я добавить прослушиватели событий в качестве зависимостей? Просто сказать, что это звучит странно, но я не уверен, какие еще зависимости нужно добавить, чтобы события работали с правильными данными.

2. Вам нужно будет добавить функции, от которых зависят эти прослушиватели событий, в массив зависимостей. Но я согласен с тем, что написал @srosser2, это определенно лучший подход для добавления непосредственно в элемент, а не с помощью крючка useEffect.

Ответ №2:

Похоже context , что состояние не может должным образом обновляться, так как оно инициализируется как пустой объект, и useEffect единственные обновления об изменениях context и единственное место setContext , которое используется , находятся в useEffect , поэтому оно никогда не запускается.

Кстати, в React вы обычно добавляете прослушиватели событий непосредственно в элемент (используя onClick или onDragEnter и т. Д.), А не используете addEventListener и получаете элементы по идентификатору, например:

 <canvas  
    onDrop={e => handleDrop(e)}
    onDragOver={e => handleDragOver(e)}
    onDragEnter={e => handleDragEnter(e)}
    onDragLeave={e => handleDragLeave(e)}>
</canvas>

 

Взгляните на эту статью: https://www.smashingmagazine.com/2020/02/html-drag-drop-api-react/

Комментарии:

1. Хорошее замечание о событиях. Я собрал часть этого кода из более старого проекта Vue, а остальное-из простого компонента перетаскивания, который я написал для чего-то другого. Какие еще зависимости я бы добавил, чтобы это работало должным образом? Даже прочитав ваш комментарий, я не уверен. <— рубрике сейчас читать, что ссылка, которая может ответить на мой вопрос.

2. Ха — Перемещение всех моих событий на холсте решило проблему. Ваше здоровье!

3. Из того, что я могу сказать, вы устанавливаете «контекст» только с помощью setContext в эффекте использования. В эффекте использования у вас есть [контекст] в конце, и это то, что React прослушивает изменения. Поскольку вы запускаете эффект использования только при изменении «контекста» и устанавливаете контекст только в эффекте использования, вы не можете должным образом обновить состояние.