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>
);
};
Related
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);
}, );
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],
);
I want to be able to make my own state change when when the return from useTransition completes. That is, currently, in the example below, isPending toggles from true to false, but that's not enough. I want to be able to execute code on that change.
That is, in the example below, I would like to possible do something similar to what useEffect does, and have the code that is returned from the startTransition execute when the pending event changes, but that does not work.
Any other options?
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
return () => { DO THIS CODE WHEN PENDING BECOMES FALSE }
});
}}
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition({
timeoutMs: 3000
});
return (
<>
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
});
}}
>
Next
</button>
{isPending ? " Loading..." : null}
<ProfilePage resource={resource} />
</>
);
}
It's is not possible within the startTransition call / useTransition config. You can use useEffect/useLayoutEffect to run some code after resource update.
useEffect/useLayoutEffect(() => {
// some code after resource state update
}, [resource])
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
Has anyone managed to create a sync callback for the update piece of the useState hook in react 16.8? I have been looking for one so that I can deal with synchronous actions with a 3rd party library and I can't seem to make one work for my needs.
If anyone has any references to people that have successfully completed this please add them here.
Cheers,
With hooks, you no longer need a callback from the setState function. Now, you can set state with the useState hook, and listen for its value to update with the useEffect hook. The optional second parameter of the useEffect hook takes an array of values to listen to for changes. In the example below, we are just monitoring one value for changes:
const Example = () => {
const [value, setValue] = useState(null);
useEffect(() => {
// do something when value changes
}, [value]);
return (
<button onClick={() => setValue('Clicked!')}>
Click Me!
</button>
);
};
Read more about the useEffect hook here.
You can use useEffect/useLayoutEffect to achieve this:
const SomeComponent = () => {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
}, [count]);
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
If you are looking for an out of the box solution, check out this custom hook that works like useState but accepts as second parameter a callback function:
import useStateWithCallback from 'use-state-with-callback';
const SomeOtherComponent = () => {
const [count, setCount] = useStateWithCallback(0, count => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
It can be installed via npm install use-state-with-callback
If you want to make synchrone layout updates, use import { useStateWithCallbackInstant } from 'use-state-with-callback'; instead.