最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React - Substitute for `setState` Callback in Functional Components? - Stack Overflow

programmeradmin5浏览0评论

We have migrated to 'React Functional Components' instead of 'Class based Component'. I cannot find the substitute logic for setState callback function. I.e, I have a functional ponent with state, and I want to create an event handler function that mutates the state multiple times in sequence, the caveat being that I dont know the current value of state (it may be true/false). The following example may make more sense.

const Example = () => {
  const [ openDoor, setOpenDoor ] = useState(false); 
  // the following handler should swich 'openDoor' state to inverse of
  // current state value. Then after setTimeout duration, inverse it again
  const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  // within setTimeout below, '!openDoor' does not work because it still
  // receives the same value as above because of async nature of 
  // state updates
  setTimeout(() => setOpenDoor(!openDoor), 500)
  }
  return(...);
}

In class based ponents, we had callback argument which would update state after previous update. How do I achieve the same in the above functional ponent using state hook?

We have migrated to 'React Functional Components' instead of 'Class based Component'. I cannot find the substitute logic for setState callback function. I.e, I have a functional ponent with state, and I want to create an event handler function that mutates the state multiple times in sequence, the caveat being that I dont know the current value of state (it may be true/false). The following example may make more sense.

const Example = () => {
  const [ openDoor, setOpenDoor ] = useState(false); 
  // the following handler should swich 'openDoor' state to inverse of
  // current state value. Then after setTimeout duration, inverse it again
  const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  // within setTimeout below, '!openDoor' does not work because it still
  // receives the same value as above because of async nature of 
  // state updates
  setTimeout(() => setOpenDoor(!openDoor), 500)
  }
  return(...);
}

In class based ponents, we had callback argument which would update state after previous update. How do I achieve the same in the above functional ponent using state hook?

Share Improve this question edited Feb 15, 2020 at 10:00 Dennis Vash 54k12 gold badges117 silver badges132 bronze badges asked Feb 15, 2020 at 9:41 KayoteKayote 15.7k26 gold badges96 silver badges152 bronze badges 1
  • I think you can do something like setOpenDoor(prevOpenDoor => !prevOpenDoor) – junwen-k Commented Feb 15, 2020 at 9:49
Add a ment  | 

6 Answers 6

Reset to default 2

I wonder if useEffect is the best solution. Specially when calling setTimeout within useEffect is going to cause an infinite loop since every time we call setOpenDoor, the app renders and then useEffect is called calling again a setTimeOut that will call a setOpenDoor function... Graphically:

setTimeout -> setOpenDoor -> useEffect -> setTimeout -> ... hell  

Of course you could use an if statement wihin useEffect the same way that @ksav suggested but that does not acplish one requirement of @Kayote:

I dont know the current value of state (it may be true/false)

Here is a solution that works without useEffect and acplish the requirement stated above:

Code working in codesandbox

There, see the importance of this piece of code:

const toggleOpenDoor = () => {
  setOpenDoor(!openDoor);
  setTimeout(() => setOpenDoor(openDoor => !openDoor), 500);
};

Since we are using setTimeout, we need to pass callback to setOpenDoor instead of the updated state. This is because we want to send the 'current' state. If we sent the new state instead, by the time that setTimeOut processes that state, it will have changed (because we did it before setTimeOut executes its callback with setOpenDoor(!openDoor);) and no changes will be made.

You can use useEffect hook to see when the state change happend.

useEffect(() => { 
  // do something
  console.log('openDoor change', openDoor)
}, [openDoor]);

You can use useEffect hook to achieve this.

setOpenDoor(!openDoor);

useEffect(() => {
   // Here your next setState function
}, [openDoor]);

For more information on hooks please check out https://reactjs/docs/hooks-effect.html

You should just using setTimeout within useEffect callback:

const App = () => {
  const [openDoor, setOpenDoor] = useState(false);

  const toggle = () => setOpenDoor(prevOpen => !prevOpen);

  useEffect(() => {
    const id = setTimeout(() => toggle(), 1000);
    return () => clearTimeout(id);
  }, [openDoor]);

  return <Container>isOpen: {String(openDoor)}</Container>;
};

import React, { useState, useEffect } from "react";

const Example = () => {
  const [openDoor, setOpenDoor] = useState(false);
  const toggleOpenDoor = () => {
    setOpenDoor(!openDoor);
  };
  useEffect(() => {
    console.log(openDoor);
    if (openDoor) {
      setTimeout(() => setOpenDoor(!openDoor), 1500);
    }
  }, [openDoor]);
  return (
    <>
      <button onClick={toggleOpenDoor}>Toggle</button>
      <p>{`openDoor: ${openDoor}`}</p>
    </>
  );
};

export default Example;

Codesandbox

I'll tell you that it works pretty much in the same way as this.setState, you just a pass a callback function which takes previous state as a parameter and returns new state(docs)

const Example = () => {
  const [openDoor, setOpenDoor] = useState(false); 

  const toggleOpenDoor = () => {
    setOpenDoor(!openDoor);
    setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
  }
  return(...);
}

In order for you know when it changes you can use useEffect callback, which's gonna be called each time something changes in the dependencies array(docs)

const Example = () => {
  const [openDoor, setOpenDoor] = useState(false); 

  useEffect(() => {
    console.log('openDoor changed!', openDoor)
  }, [openDoor])

  const toggleOpenDoor = () => {
    setOpenDoor(!openDoor);
    setTimeout(() => setOpenDoor(prevDoor => !prevDoor), 500)
  }
  return(...);
}
发布评论

评论列表(0)

  1. 暂无评论