Я не могу заставить useEffect читать ввод правильно при условном рендеринге - PullRequest
0 голосов
/ 17 мая 2019

Я пытаюсь создать компонент ввода в «статическом» режиме.В этом режиме вход заменяется диапазоном, содержащим входное значение.В этом вопросе я использую диапазон ввода.

import React from 'react';
import ReactDOM from 'react-dom';

const { useState, useEffect, } = React;

const RangeInput = ({
  inputID, min, max, defaultValue, children,
}) => {
  const [valueAfterStaticChange, setValueAfterStaticChange] = useState(
    defaultValue
  );
  const [isStatic, setIsStatic] = useState(false);
  useEffect(
    () => () => {
      // do this before component unmounts and when 'isStatic' changes,
      // but only when 'isStatic' is true
      isStatic
        || setValueAfterStaticChange(document.getElementById(inputID).value);
    },
    [isStatic]
  );
  return (
    <div>
      <label htmlFor={inputID}>{children}</label>
      <span style={{ display: isStatic || 'none', }}>
        {valueAfterStaticChange}
      </span>
      <input
        type="range"
        min={min}
        max={max}
        defaultValue={valueAfterStaticChange}
        id={inputID}
        style={{ display: isStatic && 'none', }}
      />
      <button
        onClick={() => {
          setIsStatic(!isStatic);
        }}
      >
        Toggle Static
      </button>
    </div>
  );
};

ReactDOM.render(
  <RangeInput inputID="myID" min={0} max={100} defaultValue={50}>
    My Range Input
  </RangeInput>,
  document.getElementById('root')
);

Приведенный выше код работает.Но он использует display: none для имитации «без рендеринга».Я думал, что мог бы выполнить фактическое размонтирование с помощью следующей логики:

// all unchanged until... 

  return (
    <div>
      <label htmlFor={inputID}>{children}</label>
      {isStatic ? (
        <span>{valueAfterStaticChange}</span>
      ) : (
        <input
          type="range"
          min={min}
          max={max}
          defaultValue={valueAfterStaticChange}
          id={inputID}
        />
      )}
      <button
        onClick={() => {
          setIsStatic(!isStatic);
        }}
      >
        Toggle Static
      </button>
    </div>
  );
};

Каким-то образом моя функция возврата useEffect не будет правильно считывать ввод сейчас, когда он фактически размонтируется.Я думал, что использование функции возврата в useEffect приведет к функции, которая будет работать непосредственно перед размонтированием компонента.Что я делаю не так?

Ответы [ 2 ]

1 голос
/ 17 мая 2019

Если нет причины получить значение при размонтировании, кажется, что вы можете просто отслеживать значение по мере его изменения.Таким образом, вам вообще не понадобится useEffect:

const RangeInput = ({ inputID, min, max, defaultValue, children }) => {
  const [value, setValue] = useState(defaultValue); // <-- keep track of value
  const [isStatic, setIsStatic] = useState(false);

  return (
    <div>
      <label htmlFor={inputID}>{children}</label>
      {isStatic ? (
        <span>{value}</span>
      ) : (
        <input
          type="range"
          min={min}
          max={max}
          defaultValue={value}
          onChange={e => setValue(e.target.value)} // <-- Set value onChange
          id={inputID}
        />
      )}
      <button
        onClick={() => {
          setIsStatic(!isStatic);
        }}
      >
        Toggle Static
      </button>
    </div>
  );
};
1 голос
/ 17 мая 2019

Естественно, вы не можете получить доступ к document.getElementById(inputID).value, если элемент <input> не смонтирован - он не существует!И даже если бы это было так, вы бы не хотели так понимать его значение.Это очень нереактивный способ ведения дел.У компонентов реагирования должно быть все, что им нужно, в состоянии и реквизите - если вы обнаруживаете, что обращаетесь к DOM или действуете напрямую, вы (почти всегда) идете по Темному Пути.

Вместо этого вам нужно получить Контролируемый компонент .

Контролируемый компонент не полагается на DOM для хранения чего-либо подобного «значению».Вместо этого компонент просто отображает любое значение, которое React имеет в состоянии, и предоставляет удобный обработчик событий.

Я очень, очень рекомендую прочитать документы, которые я связал, и посмотреть, сможете ли вы найти реализацию натвой собственный.Это лучший способ учиться в конце концов!Но если вы все еще не можете понять это, я вернусь и отредактирую свой ответ на простом рабочем примере.

Редактировать: вот простая рабочая демонстрация с некоторыми комментариями.

https://stackblitz.com/edit/controlled-input-demo?file=index.js

Обратите внимание, что это на самом деле довольно просто!<App> отслеживает значение ползунка в состоянии, в то время как <RangeInput> просто использует это значение в качестве реквизита и предоставляет обработчик событий для его обновления обратно в <App>.<RangeSlider> может отслеживать свое собственное значение «режима редактирования», потому что другим компонентам, вероятно, не понадобится доступ к нему (но вы могли бы «поднять» это состояние до нужного уровня!) Вам даже не нужно useEffect() здесь!

...