setState внутри setTimeout возвращает запутанные результаты - PullRequest
0 голосов
/ 07 мая 2020

Я пытаюсь показать загрузчик в течение 1,5 с после отправки формы, затем остановил загрузчик и отправил форму. поэтому я устанавливаю переменную состояния isSubmitting:

  const [isSubmitting, setIsSubmitting] = useState(false);

isSubmitting по умолчанию false, и после отправки я превращаю его в true, после этого я устанавливаю тайм-аут, чтобы установить его вернуться к false. но почему-то он не возвращается к false см. закомментированный код

const onSubmit = (e) => {
    e.preventDefault();
    setIsSubmitting(!isSubmitting);
    setTimeout(() => {
      createProfile(formData, history, edit);
      console.log(isSubmitting);// false - why?? its been set to true before the timeoute
      setIsSubmitting(!isSubmitting);
      console.log(isSubmitting);// false - if the previous log was false, this should of been true
    }, 1500);
  };
console.log(isSubmitting) // true - ??? it did not get set back to false

Ответы [ 4 ]

1 голос
/ 07 мая 2020

Обновление состояния с помощью средства обновления, предоставленного хуком useState, является асинхронным и не будет сразу отражать обновленные изменения.

т.е. 1007 *

0 голосов
/ 08 мая 2020

isSubmitting по умолчанию - false, и после отправки я переключаю его на true, после этого я устанавливаю тайм-аут, чтобы вернуть его обратно на false. но почему-то он не возвращается к false см. закомментированный код

Это - это как работают закрытия

, но как это просто js, у меня нет такой проблемы? звучит так, как будто это связано с useState?

Да, просто JS у вас такая же проблема. Я покажу вам:

let storedValue = false;

function setStoredValue(newValue) {
  storedValue = newValue
}

// useState() is basically this; more or less.
// The place where `storedValue` and `setStoredValue` are kept is differrent, 
// but that's not relevant to the issue at hand.
function gimmeTheValueAndASetter() {
  return [
    storedValue,
    setStoredValue
  ]
}

function Element(label) {
  // you create a local **constant** copy of the state at this moment
  const [localValue, setStoredValue] = gimmeTheValueAndASetter();

  // and all your logs here are based off of this local **constant**!
  console.log(label, "#1 local:", localValue, " stored:", storedValue);

  setStoredValue(!localValue);

  console.log(label, "#2 local:", localValue, " stored:", storedValue);

  setTimeout(() => {
    console.log(label, "#3 local:", localValue, " stored:", storedValue);

    setStoredValue(!localValue);

    console.log(label, "#4 local:", localValue, " stored:", storedValue);
  }, 100);
}

// render Element
Element("first render");
console.log("after first render:", storedValue);
Element("second render");
.as-console-wrapper{top:0;max-height:100%!important}

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

const onSubmit = (e) => {
  e.preventDefault();

  // we don't want to submit while another submit is still in progress
  // right?
  if(isSubmitting) return; 

  setIsSubmitting(true);
  setTimeout(() => {
    createProfile(formData, history, edit);
    setIsSubmitting(false);
  }, 1500);
};

Разве это не проще рассуждать, чем то, что у вас было раньше? isSubmitting было false, тогда я перевернул его на true, так что теперь мне нужно перевернуть его снова, чтобы вернуться к false, ...

0 голосов
/ 07 мая 2020

Может быть, немного лишнего байта, вы можете справиться с этим, используя Promise

const handleRequest = () => new Promise((resolve, reject) => {
  setTimeout(() => {
     createProfile(formData, history, edit)
       .then(response => resolve(response))
       .catch(error => reject(error));
  }, 1500);
}

const onSubmit = (event) => {
  event.preventDefault();
  setIsSubmitting(true);

  handleRequest().finally(() => setIsSubmitting(false);
};

И, возможно, второе излишество: я не уверен, почему вам нужно отправлять данные через 1,5 секунды, а не сразу, но вы можете иметь как загрузчик 1.5s, так и запрос, отправленный сразу после отправки, создав какой-то фиктивный Promise и используя Promise.all, например:

const dummyPromise = () => new Promise((resolve, reject) => {
  setTimeout(() => resolve(), 1500);
}

const onSubmit = (event) => {
  event.preventDefault();
  setIsSubmitting(true);

  Promise.all([
    dummmyPromise(),
    createProfile(formData, history, edit)
  ])
   .then(response => ...handle successfull response)
   .catch(error => ...handle request error)
   .finally(() => setIsSubmitting(false));
};

Дело в том, что вы не знаете, как долго ваш запрос будет длиться, поэтому при такой настройке вы готовы к различным ситуациям: запрос sumbmit отправляется немедленно, и Promise.all разрешится после того, как все обещания разрешатся, поэтому, если ваш запрос разрешится очень быстро, у вас все равно будет загрузчик 1,5 с, с другой стороны, если запрос будет длиться дольше чем 1,5 с, у вас все еще будет активный загрузчик, указывающий, что запрос еще не завершен. Как я уже сказал, излишество в вашей ситуации, но я надеюсь, что мой комментарий будет содержать несколько хороших идей, как улучшить обработку asyn c запросов;)

0 голосов
/ 07 мая 2020

Так работают замыкания в JS. Функции, переданные в setTimeout, получат переменную isSubmitting из начального рендеринга, поскольку isSubmitting не мутирован.

Вы можете получить функцию в качестве аргумента вместо поддержки setIsSubmitting Это означает, что isSubmitting будет обновлено значение.

setTimeout(() => {
  setIsSubmitting(isSubmitting => !isSubmitting);
}, 1500);

Надеюсь, это сработает

...