React object as useEffect dependency triggers callback on every render - javascript

I have a relatively simple React hook to debounce a value. The only twist is that I want to provide a callback which gets triggered whenever the debounced value changes. I'm aware that I could just have another useEffect inside App which listens for changes on debouncedValue, but I'd like to integrate this behavior into the reusable hook.
const useDebounce = (value, delay, { onChange }) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedValue(value);
onChange(value);
}, delay);
return () => {
clearTimeout(timeout);
};
}, [value, delay, onChange]);
return debouncedValue;
};
const App = () => {
const [value, setValue] = useState("");
const debouncedValue = useDebounce(value, 750, { onChange: (value) => console.log(value) });
return (
<div style={{ margin: 50 }}>
<input value={value} onChange={(event) => setValue(event.target.value)} />
<p>{value}</p>
<p>{debouncedValue}</p>
</div>
);
};
Now to the issue I have is that the onChange fires twice for every debounce. Apparently the onChange callback must be part of the dependency array of useEffect, even if I don't care when it updates. But this creates the problem than on every render a new object for { onChange: (value) => console.log(value) } gets created therefore reference equality thinks the useEffect dependency has changed. I could just create the object outside of my component, but that would also complicate the API of my hook.
So, my question: what is the easiest way to solve this problem? Ideally without pulling in any 3rd party packages. The fix I can think of would be to write a custom useEffect hook that does deep comparisons, but I hope someone can point me to a simpler solution.

Related

How unmounting really works?

I observed a strange behavior in React while unmounting a component. Typically, when we unmount a component, we do this.
const [isVisible, setVisibility] = useState(false)
const onKeyDown = (event) => { console.log(event) }
useEffect(() => {
window.addEventListener('keydown', onKeyDown)
return () => { window.removeEventListener('keydown', onKeyDown) }
}, [isVisible])
return (
<button onClick={() => setVisibility(!isVisible)}>Click me!</button>
)
It works perfectly fine, but when we try to achieve this same thing using an if-else statement instead of a return, unmounting doesn't work. I searched for the reason, and I found out that when the state changes, React re-renders the handler function, which results in mismatching of both the handler (old and new one), and it won't remove.
Code using if-else statement.
const [isVisible, setVisibility] = useState(false)
const onKeyDown = (event) => { console.log(event) }
useEffect(() => {
if(isVisible) window.addEventListener('keydown', onKeyDown)
else window.removeEventListener('keydown', onKeyDown)
}, [isVisible])
return (
<button onClick={() => setVisibility(!isVisible)}>Click me!</button>
)
But now my question is how it works when we write return()? Does React save the same handler function when we write return()?
I don't know if this is the answer you're looking for, but I had issues where a stateful variable was needed on my unmount function (saving a form value to localstorage), and the issue seemed to lie in the fact that the original value on initial render was being used, and would not update: (I assume because my useEffect had been set to only run at the start via ,[])
If the if/else statement you mentioned is based on a stateful variable, you likely ran into the same situation.
Here's a small code snippet of what solved things for me, the same idea likely applies for you. Just use a ref and keep the ref updated as you state value changes:
const formValueRef = useRef<any>();
useEffect(() => {
return () => {
saveEntryFormToLocalStorage(formValueRef.current); // save form to storage when the component is destroyed
}
}, []) // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
formValueRef.current = form.values;
}, [form.values]); // eslint-disable-line react-hooks/exhaustive-deps

React, useEfect and axios. infinite loop [duplicate]

Are there ways to simulate componentDidMount in React functional components via hooks?
For the stable version of hooks (React Version 16.8.0+)
For componentDidMount
useEffect(() => {
// Your code here
}, []);
For componentDidUpdate
useEffect(() => {
// Your code here
}, [yourDependency]);
For componentWillUnmount
useEffect(() => {
// componentWillUnmount
return () => {
// Your code here
}
}, [yourDependency]);
So in this situation, you need to pass your dependency into this array. Let's assume you have a state like this
const [count, setCount] = useState(0);
And whenever count increases you want to re-render your function component. Then your useEffect should look like this
useEffect(() => {
// <div>{count}</div>
}, [count]);
This way whenever your count updates your component will re-render. Hopefully this will help a bit.
There is no exact equivalent for componentDidMount in react hooks.
In my experience, react hooks requires a different mindset when developing it and generally speaking you should not compare it to the class methods like componentDidMount.
With that said, there are ways in which you can use hooks to produce a similar effect to componentDidMount.
Solution 1:
useEffect(() => {
console.log("I have been mounted")
}, [])
Solution 2:
const num = 5
useEffect(() => {
console.log("I will only run if my deps change: ", num)
}, [num])
Solution 3 (With function):
useEffect(() => {
const someFunc = () => {
console.log("Function being run after/on mount")
}
someFunc()
}, [])
Solution 4 (useCallback):
const msg = "some message"
const myFunc = useCallback(() => {
console.log(msg)
}, [msg])
useEffect(() => {
myFunc()
}, [myFunc])
Solution 5 (Getting creative):
export default function useDidMountHook(callback) {
const didMount = useRef(null)
useEffect(() => {
if (callback && !didMount.current) {
didMount.current = true
callback()
}
})
}
It is worth noting that solution 5 should only really be used if none of the other solutions work for your use case. If you do decide you need solution 5 then I recommend using this pre-made hook use-did-mount.
Source (With more detail): Using componentDidMount in react hooks
There's no componentDidMount on functional components, but React Hooks provide a way you can emulate the behavior by using the useEffect hook.
Pass an empty array as the second argument to useEffect() to run only the callback on mount only.
Please read the documentation on useEffect.
function ComponentDidMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount');
}, []);
return (
<div>
<p>componentDidMount: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<div>
<ComponentDidMount />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
useEffect() hook allows us to achieve the functionality of componentDidMount, componentDidUpdate componentWillUnMount functionalities.
Different syntaxes of useEffect() allows to achieve each of the above methods.
i) componentDidMount
useEffect(() => {
//code here
}, []);
ii) componentDidUpdate
useEffect(() => {
//code here
}, [x,y,z]);
//where x,y,z are state variables on whose update, this method should get triggered
iii) componentDidUnmount
useEffect(() => {
//code here
return function() {
//code to be run during unmount phase
}
}, []);
You can check the official react site for more info. Official React Page on Hooks
Although accepted answer works, it is not recommended. When you have more than one state and you use it with useEffect, it will give you warning about adding it to dependency array or not using it at all.
It sometimes causes the problem which might give you unpredictable output. So I suggest that you take a little effort to rewrite your function as class. There are very little changes, and you can have some components as class and some as function. You're not obligated to use only one convention.
Take this for example
function App() {
const [appointments, setAppointments] = useState([]);
const [aptId, setAptId] = useState(1);
useEffect(() => {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = aptId;
console.log(aptId);
setAptId(aptId + 1);
return item;
})
setAppointments(apts);
});
}, []);
return(...);
}
and
class App extends Component {
constructor() {
super();
this.state = {
appointments: [],
aptId: 1,
}
}
componentDidMount() {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = this.state.aptId;
this.setState({aptId: this.state.aptId + 1});
console.log(this.state.aptId);
return item;
});
this.setState({appointments: apts});
});
}
render(...);
}
This is only for example. so lets not talk about best practices or potential issues with the code. Both of this has same logic but the later only works as expected. You might get componentDidMount functionality with useEffect running for this time, but as your app grows, there are chances that you MAY face some issues. So, rather than rewriting at that phase, it's better to do this at early stage.
Besides, OOP is not that bad, if Procedure-Oriented Programming was enough, we would never have had Object-Oriented Programming. It's painful sometimes, but better (technically. personal issues aside).
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Please visit this official docs. Very easy to understand the latest way.
https://reactjs.org/docs/hooks-effect.html
Info about async functions inside the hook:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
useLayoutEffect hook is the best alternative to ComponentDidMount in React Hooks.
useLayoutEffect hook executes before Rendering UI and useEffect hook executes after rendering UI. Use it depend on your needs.
Sample Code:
import { useLayoutEffect, useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("useEffect Statements");
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect Statements");
}, []);
return (
<div>
<h1>Hello Guys</h1>
</div>
);
}
Yes, there is a way to SIMULATE a componentDidMount in a React functional component
DISCLAIMER: The real problem here is that you need to change from "component life cycle mindset" to a "mindset of useEffect"
A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it's still a funtion like for example:
const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)
mySecondFunction()
myFunction()
/* Result:
'b'
'a'
*/
That is really simple isn't it?
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
And in this specific case it's true. But what happens if I do something like that:
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
This "cleverFunction" we are defining it's not the same in every re-render of the component.
This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.
The real problem with that is that a React functional component is a function that "executes itself" several times depending on your state thanks to the useEffect hook (among others).
In short useEffect it's a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it's the array dependencies.
Leaving that undefined leaves you open to hard-to-find bugs.
Because of that it's important to know how this work, and what you can do to get what you want in the "react" way.
const initialState = {
count: 0,
step: 1,
done: false
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'doSomething') {
if(state.done === true) return state;
return { ...state, count: state.count + state.step, state.done:true };
} else if (action.type === 'step') {
return { ...state, step: action.step };
} else {
throw new Error();
}
}
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
dispatch({ type: 'doSomething' });
}, [dispatch]);
return (
<div>
<h1>Hi!</h1>
</div>
)}
useReducer's dispatch method it's static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of do it right.
Source: The Complete Guide to useEffect - By Dan Abramov
That being said if you like to experiment with things and want to know how to do it "the imperative wat" you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it's recommended to avoid it if you're not familiar with what happen with react behind curtains.
That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it's not the focus of the problem here, you can read this amazing article about useRef ). So it's the best approach to known when the first render of the component happened.
I leave an example showing 3 different ways of synchronise an "outside" effect (like an external function) with the "inner" component state.
You can run this snippet right here to see the logs and understand when these 3 functions are executed.
const { useRef, useState, useEffect, useCallback } = React
// External functions outside react component (like a data fetch)
function renderOnce(count) {
console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}
function renderOnFirstReRender(count) {
console.log(`renderOnUpdate: I executed just ${count} times!`);
}
function renderOnEveryUpdate(count) {
console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}
const MyComponent = () => {
const [count, setCount] = useState(undefined);
const mounted = useRef(0);
// useCallback is used just to avoid warnings in console.log
const renderOnEveryUpdateCallBack = useCallback(count => {
renderOnEveryUpdate(count);
}, []);
if (mounted.current === 0) {
renderOnce(count);
}
if (mounted.current === 1) renderOnFirstReRender(count);
useEffect(() => {
mounted.current = mounted.current + 1;
renderOnEveryUpdateCallBack(count);
}, [count, renderOnEveryUpdateCallBack]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
</div>
);
};
class App extends React.Component {
render() {
return (
<div>
<h1>hI!</h1>
</div>
);
}
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you execute it you will see something like this:
You want to use useEffect(), which, depending on how you use the function, can act just like componentDidMount().
Eg. you could use a custom loaded state property which is initially set to false, and switch it to true on render, and only fire the effect when this value changes.
Documentation
the exact equivalent hook for componentDidMount() is
useEffect(()=>{},[]);
hope this helpful :)

React useRef not updating consistently for conditionally rendered elements

Across my app, there are some UX logic that needs to be shared. They are triggered by events, so I wrote 2 custom hooks. Let's call one useRefWithCalc. The other hook is a more standard useEventListener, similar to this one.
useRefWithCalc calls the native useRef, has some internal handlers for UX, then calls useEventListener to attach those handlers. useRefWithCalc returns the ref created within, so another component can use this hook, and get the returned ref to attach to elements.
This has worked for me when the ref isn't attached to conditionally rendered elements.
The component looks something like this. Please take note on the 2 test logs.
const useEventListener = (event, listener, ref) => {\
...
useEffect(() => {
...
console.log("1. ref is: ", ref.current); // test logging 1.
ref.current.addEventListener(event, listener);
return () => {
ref.current.removeEventListener(event, listener);
}
}, [event, listener, ref]);
}
const useRefWithCalc = (value) => {
const ref = useRef(null);
...
const calc = () => {
// some calculations
}
...
useEventListener(event, calc, ref)
return [ref, result]
}
// works perfectly
const WorkingElement = (props) => {
const [ref, result] = useRefWithCalc(props.value);
...
return <B ref={ref} />
}
// doesn't work consistently
const ConditionalElement = (props) => {
const [state, setState] = useState(false);
const [ref, result] = useRefWithCalc(props.value)
useEffect(()=>{
if (ref && ref.current) {
ref.current.focus();
console.log("2. ref is: ", ref.current); // test logging 2
}
}, [ref])
...
return state ? <A> : <B ref={ref} />
}
The <WorkingElement /> works just as expected. The ref gets attached, and handles events with no problem.
However, in the <ConditionalElement />, when B is mounted, sometimes times test logging 1 won't fire. Test logging 2 always fires, and the ref gets the focus correctly. But this update is not passed into useEventListener
Once <B /> gets 1 subsequent update (e.g. when user inputs something), both logs will fire correctly, and the event listner gets attached correctly, and it work just as <WorkingElement />
Sorry for not posting the exact code. I feel like my approach is convoluted and might be wrong.
In React when a ref changes, it doesn't trigger a component update, and useEffects are not triggered.
I suggest to put your ref inside a state so that effects are triggered when the ref changes :
const [ref, setRef] = useState(undefined)
return (
<B ref={setRef}/>
)

React: usePrevious hook only works if there is 1 state in the component. How to make it work if there are multiple states?

The docs suggests the folllowing to get previous state:
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Per my understanding, this works fine only if there is exactly one state in the component. However consider the following where there are multiple states:
import "./styles.css";
import React, { useState, useEffect, useRef, useContext } from "react";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
export default function App() {
const [count, setCount] = useState(0);
const [foo, setFoo] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<button onClick={() => setFoo(f => f+1)}> Update foo </button>
<h1>Now: {count}, before: {prevCount}</h1>
</div>);
}
Sandbox: https://codesandbox.io/s/little-feather-wow4m
When a different state (foo) is updated, the usePrevious hook returns the latest value for count, as opposed to the previous one).
Is there a way to reliably get the previous value for a state/prop when there are multiple states?
I don't think this is the right approach.
How about a custom hook that sets up the state and returns a custom setter function that handles this logic for you.
function useStateWithPrevious(initial) {
const [value, setValue] = useState(initial)
const [prev, setPrev] = useState(initial)
function setValueAndPrev(newValue) {
if (newValue === value) return // optional, depends on the logic you want.
setPrev(value)
setValue(newValue)
}
return [prev, value, setValueAndPrev]
}
Which you would use like:
function MyComponent() {
const [prevCount, count, setCount] = useStateWithPrevious(0)
}
I was able to create an altered version of your hook that does seem to work:
function usePrevious(value) {
const ref = useRef([undefined, undefined]);
if (ref.current[0] !== value) {
ref.current = [value, ref.current[0]];
}
return ref.current[1];
}
Playground here.
Keeping track of only the previous value of some state is not a very common use case. Hence there's no need to overthink it or try to "trick" React with refs to achieve a slightly shorter syntax. There's almost always a few ways to get the same result in a more straightforward and maintainable manner.
React's docs also stress that this suggested approach is only for edge cases.
This is rarely needed and is usually a sign you have some duplicate or redundant state.
If the previous count is de facto part of the application state (it's used in rendering just like the current count), it's counter productive to not just store it as such. Once it's in state, it's just a matter of making state updates in event listeners update all parts of the state in one go, making it inherently safe with React's concurrent features.
Method 1: Set multiple state variables at the same time
Just create an additional state variable for the old value, and make your handler set both values.
const initialCount = 0;
function App() {
const [count, setCount] = useState(initialCount);
const [prevCount, setPrevCount] = useState(initialCount);
return <>
<button
onClick={() => {
// You can memoize this callback if your app needs it.
setCount(count + 1);
setPrevCount(count);
}}
>Increment</button>
<span>Current: {count} </span>
<span>Previous: {prevCount} </span>
</>
}
You can almost always do this instead, it offers the same functionality as usePrevious and obviously will never lead to the application using the wrong combination of values. In fact, because of batched state updates since React 18 there's no performance penalty in calling 2 setters in one event handler.
Using a hook like usePrevious doesn't really bring any overall benefits. Clearly both the current and the previous value are pieces of state your application needs for rendering. They can both use the same simple and readable syntax. Just because usePrevious is shorter doesn't mean it's easier to maintain.
Method 2: useReducer
If you want to avoid the 2 function calls in the event listener, you can use useReducer to encapsulate your state. This hook is particularly well suited for updates of complex but closely related state. It guarantees the application state transitions to a new valid state in one go.
const initialState = { count: 0, prevCount: 0, foo: 'bar' };
function countReducer(state, action) {
switch (action.type) {
case: 'INCREMENT':
return {
...state,
count: state.count + 1,
prevCount: state.count,
};
case 'DO_SOMETHING_ELSE':
// This has no effect on the prevCount state.
return {
...state,
foo: payload.foo,
}
}
return state;
}
function App() {
const [
{ count, prevCount },
dispatch
] = useReducer(countReducer, initialState)
return <>
<button
onClick={() => {
dispatch({ type: 'INCREMENT' });
}}
>Increment</button>
<button
onClick={() => {
dispatch({
type: 'DO_SOMETHING_ELSE',
payload: { foo: `last update: ${prevCount} to ${count`} },
);
}}
>Do foo</button>
<span>Current: {count} </span>
<span>Previous: {prevCount} </span>
</>
}

React - Substitute for `setState` Callback in Functional Components?

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 component 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 components, we had callback argument which would update state after previous update. How do I achieve the same in the above functional component using state hook?
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 accomplish 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 accomplish 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]);
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(...);
}
:)
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.org/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

Categories

Resources