How to make a delay in React - reactjs

When you click on the button, another screen is rendered:
<button className="button _button_two_column" onClick={()=> dispatch({type: "NEXT_SCREEN"})}>
if (action.type === 'NEXT_SCREEN') {
return {
...state,
currentScreenIndex: state.currentScreenIndex + 1,
}
}
/when 1 is added to the currentScreenIndex the screen changes/
I need screen 1 to open for 3 seconds when I press the button and then screen 2
How to do it? I tried to do it using setTimeout, but nothing happened

I believe you are using the useReducer hook. You can do the dispatch inside the callback of setInterval. You need to carefully handle the memory leaks as well.
Try like this:
Add the following hooks to your component. trigger is to trigger the screen transitions. We call setInterval only once (using if check) to avoid memory leaks and clear it when the component unmounts.
const [trigger, setTrigger] = useState(false);
useEffect(() => {
let interval;
if (trigger) {
interval = setInterval(() => dispatch({ type: "NEXT_SCREEN" }), 3000);
}
return () => clearInterval(interval);
}, [trigger]);
Change the trigger to true when the button click happens
<button
className="button _button_two_column"
onClick={() => setTrigger(true)}
></button>
Demo code:

I suggest to use setTimeout() function to make a delay.
And avoid to use javascript function in html.
const onClick = () => {
setTimeout(dispatch({type: "NEXT_SCREEN"}), 1000)
}
<button className="button _button_two_column" onClick={onClick}>

The solution to my task:
useEffect(() => {
let interval;
if (stateScreen.currentScreenIndex===4) {
interval = setInterval(() => dispatch({ type: "NEXT_SCREEN" }), 2000);
}
return () => clearInterval(interval);
}, );

Related

Create the setTimeout in the parent and closes it in the children

I have a specific problem that is keeping me awake this whole week.
I have a parent component which has a pop-up children component. When I open the page the pop-up shows off and after 5 seconds it disappears with a setTimeout.
This pop-up has an input element in it.
I want the pop-up to disappear after 5 seconds or if I click to digit something in the input. I tried to create a timerRef to the setTimeout and closes it in the children but it didn't work.
Can you help me, please? Thanks in advance.
ParentComponent.tsx
const ParentComponent = () => {
const [isVisible, setIsVisible] = useState(true)
timerRef = useRef<ReturnType<typeof setTimeout>>()
timerRef.current = setTimeout(() => {
setIsVisible(false)
}, 5000)
useEffect(() => {
return () => clearTimeout()
})
return (
<div>
<ChildrenComponent isVisible={isVisible} inputRef={timerRef} />
</div>
)
}
ChildrenComponent.tsx
const ChildrenComponent = ({ isVisible, inputRef}) => {
return (
<div className=`${isVisible ? 'display-block' : 'display-none'}`>
<form>
<input onClick={() => clearTimeout(inputRef.current as NodeJS.Timeout)} />
</form>
</div>
)
}
You're setting a new timer every time the the component re-renders, aka when the state changes which happens in the timeout itself.
timerRef.current = setTimeout(() => {
setIsVisible(false);
}, 5000);
Instead you can put the initialization in a useEffect.
useEffect(() => {
if (timerRef.current) return;
timerRef.current = setTimeout(() => {
setIsVisible(false);
}, 5000);
return () => clearTimeout(timerRef.current);
}, []);
You should also remove the "loose" useEffect that runs on every render, this one
useEffect(() => {
return () => clearTimeout();
});

React SetState callback is synchronous

I have a simple react component and iam playing with the state a little bit.
I noticed when i pressed the button, the "first" executes along with "inside1". Afterwards "second" and then "HEYY STATE". I thought that setState executes asynchronously but it seems that the first one executes synchronously.
Why only the first callback is executed synchronously?
On React 17v.
const Test= () => {
const [test, setTest] = useState(0);
console.log("HEYY STATE", test);
return (
<button
type="button"
onClick={() => {
setTest((e) => {
console.log("first", e);
return 1;
});
setTest((e) => {
console.log("second", e);
return 2;
});
console.log("inside1");
}}
>
Hello
</button>
);
};

Click event added in useEffect after changing state by onClick is executed in same moment

React 18 changed useEffect timing at it broke my code, that looks like this:
const ContextualMenu = ({ isDisabled }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleMenu = useCallback(
() => {
if (isDisabled) return;
setIsExpanded((prevState) => !prevState);
},
[isDisabled],
);
useEffect(() => {
if (isExpanded) {
window.document.addEventListener('click', toggleMenu, false);
}
return () => {
window.document.removeEventListener('click', toggleMenu, false);
};
}, [isExpanded]);
return (
<div>
<div class="button" onClick={toggleMenu}>
<Icon name="options" />
</div>
{isExpanded && <ListMenu />}
</div>
);
};
The problem is, toggleMenu function is executed twice on button click - first one is correct, it's onClick button action, which changes state, but this state change executes useEffect (which adds event listener on click) and this click is executed on the same click, that triggered state change.
So, what should be correct and most "in reactjs spirit" way to fix this?
Your problem is named Event bubbling
You can use stopPropagation to fix that
const toggleMenu = useCallback(
(event) => {
event.stopPropagation();
if (isDisabled) return;
setIsExpanded((prevState) => !prevState);
},
[isDisabled],
);

React useContext & useEffect show stale data

https://codesandbox.io/s/react-usecontextuseeffect-stale-data-bug-l81sn
In a component I use useEffect to do something when a certain value changes. Now it's only the value of a simple counter, but in the real world it would be a array where items are removed or added etc. A little bit more complex.
In the useEffect I also have a resize detection. Again, in this example not very interesting.
const App = props => {
const [count, dispatch] = useReducer((state, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}, 0);
return (
<CountContext.Provider value={{ count, dispatch }}>
<div className="App">
<h1>App</h1>
<Counter />
</div>
</CountContext.Provider>
);
};
const Counter = () => {
const counter = useContext(CountContext);
useEffect(() => {
window.addEventListener('resize', () => {
console.log(counter.count);
})
},[counter])
return (
<div className="Counter">
<p>Counter: {counter.count}</p>
<input
type="button"
value="+"
onClick={() => counter.dispatch({ type: 'INCREMENT' })}
/>
<input
type="button"
value="-"
onClick={() => counter.dispatch({ type: 'DECREMENT' })}
/>
</div>
);
};
The issue is that when I resize the viewport the console.log(counter.count) shows all previous values:
The issue is a memory leak in the useEffect() method. You need to clean up on re-renders. I forked your sandbox and tested it with this and it works as expected:
useEffect(() => {
const resizeEvent = () => {
console.log(counter.count);
};
window.addEventListener("resize", resizeEvent);
return () => {
window.removeEventListener("resize", resizeEvent);
};
}, [counter]);
Notice the return for the clean up and the refactor of your code to a called function so that it can be correctly removed on dismount.
You add a new one eventListener every time your state changes. Three changes - three eventListeners. Moreover, when your Counter component is unmounted, the listeners keep alive that cause memory leaks.
First of all, you can take this part outside of useEffect:
window.addEventListener('resize', () => {
console.log(counter.count);
})
Or you should use empty array as dependencies list in useEffect, then it will fire just once:
useEffect(() => {
}, []) // empty array here says 'do it once'
And finally, useEffect is a perfect place for fetching data or subscribing for events etc. But do not forget to clear all it up after component is not needed anymore. For doing this, return cleaning function in useEffect:
useEffect(() => {
// your main logic here
...
// cleaning up function:
return () => {
removeEventListener, unsubscribe etc...
}
}, [])

setState inside setTimeout react hooks

I am currently trying to build a rock-paper-scissor and what I intend to achieve are this logic:
after the start button clicked, a player has 3seconds to pick a weapon, if not, a random weapon will be picked for the player.
The problem:
When I picked a weapon under the 3seconds, it works just fine. But, when I intentionally let the setTimeout triggered, it is not updating the state automatically. I suspected the if conditions are not met, but I don't know why that happen.
Here is the code snippet:
//custom hooks//
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const weapons= ['rock', 'weapon', 'scissors']
const App = () => {
const [p1Weapon, setp1Weapon] = useState("");
const prevWeapon = usePrevious(p1Weapon);
const getPlayerTimeout = (playerRef, setPlayer, time) => {
setTimeout(() => {
if (playerRef === "") {
setPlayer(weapons[Math.floor(Math.random() * weapons.length)]);
}
}, time);
};
const startGame = () => {
getPlayerTimeout(prevWeapon, setp1Weapon, 3000);
}
return (
...
<div>
<button
className="weaponBtn"
onClick={() => {
setp1Weapon("rock");
}}
>
rock
</button>
<button className="weaponBtn" onClick={() => setp1Weapon("paper")}>
paper
</button>
<button className="weaponBtn" onClick={() => setp1Weapon("scissors")}>
scissor
</button>
<button type="button" onClick={startGame}>
Start!
</button>
</div>
)
Thanks!
if all you want to do is set a state after x time you can do this very easily like this
this.setState({isLoading: true},
()=> {window.setTimeout(()=>{this.setState({isLoading: false})}, 8000)}
);
this should set the value of isLoading to false after 8 seconds.
I hope it helps

Resources