This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last month.
Using react-chessboard with chess.js. I modified the example code at: https://www.npmjs.com/package/react-chessboard.
My code:
import { Chessboard } from "react-chessboard";
import { useState } from "react";
import { Chess } from "chess.js";
const Board = () =>{
const [game, setGame] = useState(new Chess());
function makeAMove(move) {
const gameCopy = new Chess();
gameCopy.loadPgn(game.pgn());
gameCopy.move(move);
setGame(gameCopy);
}
function onDrop(sourceSquare, targetSquare) {
makeAMove({
from: sourceSquare,
to: targetSquare,
});
if(game.isGameOver()){
if(game.isStalemate() || game.isThreefoldRepetition()){
alert("Stalemate")
}
if(game.turn() == "b"){
alert("White won")
}
else if(game.turn() == "w"){
alert("Black won")
}
}
}
return (
<>
<div style = {{width: '50%',alignItems: 'center', marginLeft: '25%',justifyContent: 'center'}}>
<Chessboard position={game.fen()} onPieceDrop={onDrop} id="BasicBoard"/>
</div>
</>
);
}
export default Board;
Why is the isGameOver() one move behind for me? If white checkmates black then the "White won" alert pops up only after black tries to make another move after being checkmated and vice versa.
It's because makeAMove updates the state but the local instance of game that you check is still the same that it was before the state update. Only on the next render does it get the updated game from the state. Check out The useState set method is not reflecting a change immediately to get a better understanding of why this happens.
You can fix this by running the check on the gameCopy constant when you write it to the state but a more appropriate solution is to move your checks into an effect.
You'll need to import useEffect from React:
import { useState, useEffect } from "react";
Then change your code like this:
function onDrop(sourceSquare, targetSquare) {
makeAMove({
from: sourceSquare,
to: targetSquare,
});
}
useEffect(function() {
if(game.isGameOver()){
if(game.isStalemate() || game.isThreefoldRepetition()){
alert("Stalemate");
}
if(game.turn() == "b"){
alert("White won");
}
else if(game.turn() == "w"){
alert("Black won");
}
}
}, [ game ]);
The effect has the game constant as a dependency, which means that it runs every time game changes and will always run the checks and the latest state.
I would consider storing game in a ref instead of creating a new one every time you want to make a move
import { Chessboard } from "react-chessboard";
import { useState, useReducer } from "react";
import { Chess } from "chess.js";
const Board = () => {
const game = useRef();
const [_, rerender] = useReducer((x) => x + 1, 0);
useEffect(() => {
if (!ref.current) {
ref.current = new Chess();
}
}, []);
function onDrop(sourceSquare, targetSquare) {
game.current.move({
from: sourceSquare,
to: targetSquare,
});
rerender()
if (game.current.isGameOver()) {
if (game.current.isStalemate() || game.current.isThreefoldRepetition()) {
alert("Stalemate");
}
if (game.current.turn() == "b") {
alert("White won");
} else if (game.current.turn() == "w") {
alert("Black won");
}
}
}
return (
<>
<div
style={{
width: "50%",
alignItems: "center",
marginLeft: "25%",
justifyContent: "center",
}}
>
<Chessboard
position={game.current.fen()}
onPieceDrop={onDrop}
id="BasicBoard"
/>
</div>
</>
);
};
export default Board;
Because calling setGame does not immediately change the value of game. It queues a change that will resolve after render (explained in the new React docs site).
One way to solve this would be to inline your makeAMove function and use the updated game in the rest of your event handler, like this:
function onDrop(sourceSquare, targetSquare) {
const updatedGame = new Chess();
updatedGame.loadPgn(game.pgn());
updatedGame.move({
from: sourceSquare,
to: targetSquare,
});
setGame(updatedGame);
if (updatedGame.isGameOver()) {
if (updatedGame.isStalemate() || updatedGame.isThreefoldRepetition()) {
alert('Stalemate');
}
if (updatedGame.turn() == 'b') {
alert('White won');
} else if (updatedGame.turn() == 'w') {
alert('Black won');
}
}
}
Related
I am trying to implement locomotive-scroll with my react project but I run into 2 issues.
One of them is that react strict mode (I think) created 2 instances of the locomotive-scroll which resulted in multiple problems.
The second problem is that the gsap scrollTriggers that I use are created before the locomotive scroll resulting in some glitches because the scrollProxy does not update the actual scroll position properly because the locomotive scroll is created afterward.
Here is how I implemented the locomotive-scroll and made it work with scrollTrigger.
import React, { useEffect } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
const useLocoScroll = (start) => {
gsap.registerPlugin(ScrollTrigger);
useEffect(() => {
if (!start) return;
const scrollEl = document.querySelector('.App');
let locoScroll = new LocomotiveScroll({
el: scrollEl,
smooth: true,
multiplier: 1,
});
locoScroll.on('scroll', ScrollTrigger.update);
ScrollTrigger.scrollerProxy(scrollEl, {
scrollTop(value) {
if (locoScroll) {
return arguments.length
? locoScroll.scrollTo(value, 0, 0)
: locoScroll.scroll.instance.scroll.y;
}
return null;
},
scrollLeft(value) {
if (locoScroll) {
return arguments.length
? locoScroll.scrollTo(value, 0, 0)
: locoScroll.scroll.instance.scroll.x;
}
return null;
},
getBoundingClientRect() {
return {
top: 0,
left: 0,
width: window.innerWidth,
height: window.innerHeight,
};
},
// pinType: document.querySelector('.App').style.transform
// ? 'transform'
// : 'fixed',
});
const lsUpdate = () => {
if (locoScroll) {
locoScroll.update();
}
};
ScrollTrigger.addEventListener('refresh', lsUpdate);
ScrollTrigger.refresh();
// return () => {
// if (locoScroll) {
// ScrollTrigger.removeEventListener('refresh', lsUpdate);
// locoScroll.destroy();
// locoScroll = null;
// }
// };
console.log('LocoScroll created');
}, [start]);
};
export default useLocoScroll;
I've tried removing loco-scroll if it already exists (commented code in the example above), but if I do so, the scrollTrigger won't work at all.
Also, I am not a big fan of using setTimeout for scrollTrigger to allow the locoScroll to be created before them.
Using the library "# lottiefiles/react-lottie-player"
You need to get lottieRef to interact with animation, but I get null.
Code reference: https://codesandbox.io/s/great-rgb-7dp4j0?file=/src/HorizontalPicker/HorizontalPicker.jsx
import React, {useEffect, useRef} from "react";
import {Player} from "#lottiefiles/react-lottie-player";
export default function App() {
const player = useRef(null)
const lottie = useRef(null)
useEffect(() => {
if(lottie && lottie.current){
console.log(lottie.current) //return null
}
}, [])
return (
<div className="App">
<Player
lottieRef={data => lottie.current = data}
ref={player}
onEvent={event =>{
if(event === "load"){
lottie.current.play() //nothing
}
}}
keepLastFrame={true}
autoplay={false}
loop={true}
src={"https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json"}
style={{width: "100%", height: "2.5em", padding: "0", margin: "0"}}/>
</div>
);
}
There is also an interesting point.
If you output lottie, we get an object with null (while there is something inside it)
And if you output lottie.current, we get null.
Reference to an example of this thing: https://ibb.co/RQWxLkJ
Can you pass the lottie ref into your ref like the following (see ref on the div):
export default function App() {
const player = useRef(null)
const lottie = useRef(null)
useEffect(() => {
if(lottie && lottie.current){
console.log(lottie.current) //return null
}
}, [])
return (
<div className="App">
<Player
lottieRef={data => lottie.current = data}
ref={player}
onEvent={event =>{
if(event === "load"){
lottie.current && lottie.current.play() //nothing
}
}}
keepLastFrame={true}
autoplay={false}
loop={true}
src={"https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json"}
style={{width: "100%", height: "2.5em", padding: "0", margin: "0"}}/>
</div>
);
}
This way I am able to get the animation object when I console lottie.current
I'd prefer to use useState to save the animationData rather than a ref. But there was also an issue on the player where the 'load' event was firing but the player hadn't finish setting its internal instance of the animation therefor calling play() wouldn't work. This was happening only on functional components in React that's why it went undiscovered.
I've made a few changes to your code and to the player, using v1.5.2 you should be able to accomplish what you're looking for:
import css from "./HorizontalPicker.module.css";
import React, { useRef, useState, useEffect } from "react";
import { Player } from "#lottiefiles/react-lottie-player";
const HorizontalPicker = () => {
const player = useRef(null);
// const lottie = useRef(null);
const [lottie, setLottie] = useState(null);
useEffect(() => {
if (lottie) {
console.log(" Lottie animation data : ");
console.log(lottie);
// You can also play by calling the underlying lottie method
// lottie.play();
}
}, [lottie]);
return (
<>
<Player
onEvent={(event) => {
// console.log(event);
if (event === "instanceSaved" && player && player.current) {
console.log("Playing animation..");
player.current.play();
}
}}
lottieRef={(data) => {
setLottie(data);
}}
ref={player}
keepLastFrame={true}
autoplay={false}
loop={true}
src={
"https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json"
}
/>
</>
);
};
export default HorizontalPicker;
Sandbox:
https://codesandbox.io/s/lottie-react-functional-component-2bs8fs?file=/src/HorizontalPicker/HorizontalPicker.jsx
Cheers!
Well, according to official documentation https://github.com/LottieFiles/lottie-react, lottieRef represents a callback function which is fired by Player component (and this function returns AnimationFrame object)
I'm not familiar with this library, and whatever I'll describe next are just my assumptions :) Seems that whenever player "plays", it displays frames one-by-one (from json file in "src" attribute"). And whenever player displays frames from .json file - Player fires "lottieRef" event which you utilize to set lottie.current. And player starts displaying frames only when it loads .json data using "src" parameter in Player definition (see network tab in your browser to ensure that additional http request presents)
And in this case everything seems pretty logical: you try to access "lottie" variable in useEffect of your component but it's empty as far as Player did not manage to display any frame yet because the callback (lottieRef) did not fire yet as far as .json file is not loaded yet. No matter if .json file is large or small, Player requests it via additional http call, which requires some (even minital) amount time. And useEffect fires before .json is loaded immediately after rendering DOM (that's how ReactJS works)
On the other hand, if you delay a bit request to "lottie" ref - you'll see that it is populated:
Code example:
import React, { useEffect, useRef } from 'react';
import { Player } from '#lottiefiles/react-lottie-player';
export default function App() {
const player = useRef(null);
const lottie = useRef(null);
useEffect(() => {
setTimeout(() => console.log(lottie), 50);
}, []);
return (
<div className="App">
<Player
lottieRef={(data) => (lottie.current = data)}
ref={player}
keepLastFrame={true}
autoplay={false}
loop={true}
src={
'https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json'
}
/>
</div>
);
}
So if you delay onEvent callback event a bit, "lottie" ref would be initialized by that moment and .play() would work:
onEvent={(event) => {
event === 'load' &&
setTimeout(
() => lottie.current && lottie.current.play()
);
}
}
BUT, if the only purpose you have is to execute Player whenever it's ready - why not to use "autoplay" built-in property ? (autoplay={true})
import React, { useRef } from 'react';
import { Player } from '#lottiefiles/react-lottie-player';
export default function App() {
const player = useRef(null);
const lottie = useRef(null);
return (
<div className="App">
<Player
lottieRef={(data) => (lottie.current = data)}
ref={player}
keepLastFrame={true}
autoplay={true}
loop={true}
src={
'https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json'
}
/>
</div>
);
}
Hope it'll help
Is there a proper , established way to register either User permormed single click or double click on same JSX element inside of Function component. After reading articles on stackOverflow and watching youtube, the easiest solution for beginner like me was to create custom hook - useClickHook, and to use callback inside setTimeout api. In App component I'm using useEffect hook,
clickHook value is inside of array of dependencies. On first render its 0 , after first click = 1 , if doubleClick = 2; inside of If() statement in useeffect - console.log() reperesents function to be invoked. and after i'm setting clickHook value back to default 0.
Here is what I coded (minimal reproducible example)
import { useState, useEffect } from 'react';
export const useClickHook = (detail) => {
const [clickDetail, setClickDetail] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setClickDetail(detail)
}, 200);
return () => {
clearTimeout(timer);
}
}, [detail]);
return clickDetail;
}
import { useEffect, useState } from 'react';
import { useClickHook } from './ClickHook';
function App() {
const [click, setClick] = useState(0);
const clickHook = useClickHook(click);
useEffect(() => {
if (clickHook === 1) {
console.log('single click')
}
if (clickHook === 2) {
console.log('double click')
}
setClick(0);
},[clickHook])
return (
<div className="App">
Hello World
<button
onClick={(e) => {
setClick(e.detail);
}}
onDoubleClick={(e) => {
setClick(e.detail);
}}
>
Click
</button>
</div>
);
}
export default App;
How to improve it?
Will be glad for every suggestions.
Edited!!!
So one way to improve is simple to remove onDoubleClick event handler from button JSX element .
Following code can be safely deleted
onDoubleClick={(e) => {
setClick(e.detail);
}}
Thus , on double click setClick will be called only twice , not 3 times like in example proposed originaly , and the rest will be done by the custom hook as before.
Pretty sure you can already do this in React with no special code required.
Demo here
export default function Demo() {
const handleClick = (event) => {
console.log(event.detail);
switch (event.detail) {
case 1: {
console.log("single click");
break;
}
case 2: {
console.log("double click");
break;
}
default: {
break;
}
}
};
return (
<div>
<div>
<button onClick={handleClick}>Double click</button>
</div>
</div>
);
}
I have a Flatlist component that passes its state and its setState to a child component. The child component does change the state of the parent (tried showing state using setinterval and console.log) but I can't listen when the change occurs. I tried using
useEffect(()=>{
console.log(`listState:`, state);
},[state]);
But it never fires except on mounting. Here is my code.
// in CheckboxFlatList.js (parent)
import React, { useState, useEffect, useRef } from 'react';
import { View, StyleSheet, Dimensions, Text } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import CheckBox from "./Checkbox";
function CheckboxFlatList(props) {
const [state, setState] = useState([]);
useEffect(() => {
console.log(`listState:`, state);
},[state]);
// setInterval(() => {
// console.log(`listState:`, state);
// },3000 );
return (
<View>
<FlatList
data={data}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<View>
<View>
<CheckBox id={item.id.toString()} listState={state} stateMerge={setState} isChecked={state.some(x => x === item.id.toString())} />
<Text>{item.name}</Text>
</View>
</View>
)}
/>
</View >
);
}
// in Checkbox.js (child)
import React, { useState, useEffect, useRef, memo } from 'react';
import { CheckBox } from 'react-native-elements';
export default memo(
function Checkbox(props) {
let { listState, stateMerge, isChecked, id } = props;
const [toggleCheckBox, setToggleCheckBox] = useState(isChecked);
const mounted = useRef();
useEffect(() => {
if (!mounted.current) { // do componentDidMount logic
mounted.current = true;
} else { // do componentDidUpdate logic
if (toggleCheckBox) {
if (!listState.some(x => x === id)) { //to avoid a duplication bug
listState.push(id);
}
} else {
while (listState.some(x => x === id)) { //to avoid a duplication bug
let index = listState.indexOf(id);
if (index !== -1) listState.splice(index, 1);
}
}
stateMerge(listState);
}
});
return (
<CheckBox checked={toggleCheckBox} size={40} onPress={() => {
setToggleCheckBox(!toggleCheckBox);
}} />
);
});
Thank you very much for your help !
The accepted answer is a band-aid solution and introduces another bug: the effect will be fired if a no-op update is performed on the state.
The useEffect callback is not fired because the referential identity of the dep is unchanged. Forcing a new referential identity by redefining an object only treats the symptom. Generally you should aim to address the root cause instead.
In your case the referential identity not changing is a symptom of a violation of an assumption of React state. Changes to state should always be done using the appropriate setter function/method, never by mutating.
The offending code in the following block mutates React state.
if (toggleCheckBox) {
if (!listState.some(x => x === id)) { //to avoid a duplication bug
listState.push(id);
}
} else {
while (listState.some(x => x === id)) { //to avoid a duplication bug
let index = listState.indexOf(id);
if (index !== -1) listState.splice(index, 1);
}
}
stateMerge(listState);
We can fix it by eliminating mutation. When we write it without using mutation the identity naturally changes only when the value changes.
stateMerge(listState =>
if (toggleCheckBox) {
if (!listState.some(x => x === id)) { //to avoid a duplication bug
return [...listState, id];
} else {
return listState;
}
} else {
return listState.filter(x => x !== id);
}
);
You should see useEffect firing any time the state changes and not firing when it doesn't.
in your Checkbox.js Where you calling stateMerge/setState callback
pass listState to stateMerge like this
stateMerge([...listState])
will fires useEffect correctly
I need check my conditions on react hooks when startup or change value
I try that by this code but I cant run without Btn
import React,{useEffect} from 'react';
import { StyleSheet, View } from 'react-native';
import * as Permissions from 'expo-permissions';
import { Notifications} from 'expo';
export default function NotificationsTest() {
const y = 5;
const askPermissionsAsync = async () => {
await Permissions.askAsync(Permissions.USER_FACING_NOTIFICATIONS);
};
const btnSendNotClicked = async () => {
if(y===5){
await askPermissionsAsync();
Notifications.presentLocalNotificationAsync({
title: "Title",
body: "****** SUBJ *******",
ios:{
sound:true,
},
android:{
sound:true,
color:'#512da8',
vibrate:true,
}
});
}else{
} }
useEffect(()=>{
return btnSendNotClicked
}
,[])
return (
<View style={styles.container}>
</View>
);
}
and I just want to confirm is it good practice to checking this kind of condition in useEffect ?
Hi on startup useEffect with empty array will be fired and this will be happened during the page load (first time), so you can write your condition there. here is an example:
useEffect(()=> {
// Your condition here
}, []);
If you have a variable like value then you can write another useEffect like below and set that variable (value) in second parameter of useEffect as an array
const [value, setValue] = useState('');
useEffect(()=> {
// Your condition here
}, [value]);
const y = 5;
//this one will run every time the component renders
//so you could use it to check for anything on the startup
useEffect(()=> {
// Your condition here
}, []);
Otherwise you could add a value or array of values that you want to track for changes so this useEffect bellow Will only run if the y const changes so you could add an if check there to check if y===5 also this useEffect will run on the first initial of the const y so its perfect in your case
}
useEffect(()=> {
// will run every time y const change and on the first initial of y
if (y === 5)
{
//do your stuff here
}
}, [y]);