How React manage state updates wrapped in async block - reactjs

In this code i expect when i clicked asycn change button to have output:
num1: 6
num1: 7
as each state update in then() block so React does not batch the updates
but i got 2 lines:
num1: 6
import React, { useState } from "react";
import { Button } from "react-bootstrap";
const Test = (props) => {
const [num1, setNum1] = useState(5);
const [num2, setNum2] = useState(6);
const syncClickHandler = () => {
setNum1(num1 + 1);
setNum1(num1 + 1);
};
console.log("num1 " + num1);
const asyncClickHandler = () => {
Promise.resolve()
.then(() => {
setNum1(num1 + 1);
})
.then(() => {
setNum1(num1 + 1);
});
};
return (
<div>
num1: {num1} , num2: {num2} <br />
<Button onClick={syncClickHandler}>sync change</Button>
<Button onClick={asyncClickHandler}>async change</Button>
</div>
);
};
export default Test;
why i got this output

React does not batch state updates in async code (yet v17, may change in future).
Moreover, you have closure on num1 value (num1 === 6), so the second setNum call won't trigger additional render (prev value 7 === curr value 7).
Try using functional updates and discard the closure:
const asyncClickHandler = () => {
Promise.resolve()
.then(() => {
setNum((prev) => prev + 1);
})
.then(() => {
setNum((prev) => prev + 1);
});
};
A full example, notice the number of logs, sync set state are batched and async aren't.
const Test = () => {
const [num, setNum] = useState(0);
const syncClickHandler = () => {
setNum((prev) => prev + 1);
setNum((prev) => prev + 1);
};
const asyncClickHandler = () => {
Promise.resolve()
.then(() => {
setNum((prev) => prev + 1);
})
.then(() => {
setNum((prev) => prev + 1);
});
};
console.log("num " + num);
return (
<div>
num1: {num} <br />
<button onClick={syncClickHandler}>sync change</button>
<button onClick={asyncClickHandler}>async change</button>
</div>
);
};

Related

I am trying to implement a counter using react but cant seem to be able to clear the intervals properly

I am fairly new to react and started out with a basic project but I'm struggling with a counter application that I am trying to make wherein the auto increment and the auto decrement function perform simultaneously so the count is not functioning properly. Any help would be appreciated. Thanks in advance.
intervalId is a variable that has been defined globally in the component. The button click handlers are as mentioned below.
const stopInterval = () => {
clearInterval(intervalId);
intervalId = null;
};
const handleAutoDecrement = () => {
stopInterval();
if (!intervalId) {
intervalId = setInterval(() => {
setCounter((prev) => prev - 1);
}, 1000);
}
};
const handleAutoIncrement = () => {
stopInterval();
if (!intervalId) {
intervalId = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
}
};
I tried clearing the intervals in a return call back prior to this but got the same result so I am completely clueless so as to do what now.
You are storing intervalId in local variable and after every re-render its value gets undefined. You will have to store the value in a state so that when counter value changes and it renders again it must persist the intervalId value.
import React, { useState } from 'react';
import './style.css';
function CounterComponent() {
const [counter, setCounter] = useState(0);
const [intervalId, setintervalId] = useState();
const stopInterval = () => {
clearInterval(intervalId);
setintervalId();
};
const handleDecrement = () => {
setCounter((prev) => prev - 1);
};
const handleAutoDecrement = () => {
stopInterval();
// if (!intervalId) {
setintervalId(setInterval(() => {
setCounter((prev) => prev - 1);
}, 1000));
console.log('interca', intervalId);
// }
};
const handleAutoIncrement = () => {
stopInterval();
// if (!intervalId) {
setintervalId(setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000));
// }
};
const handleIncrement = () => {
setCounter((prev) => prev + 1);
};
return (
<>
<div className="counterClass">{counter}</div>
<br />
<br />
<button onClick={handleDecrement} className="decrementButton">
Decrement
</button>
<button onClick={handleAutoDecrement} className="autoDecrementButton">
AutoDecrement
</button>
<button onClick={handleAutoIncrement} className="autoIncrementButton">
AutoIncrement
</button>
<button onClick={handleIncrement} className="incrementButton">
Increment
</button>
<button onClick={stopInterval} className="incrementButton">
Stop
</button>
</>
);
}
export default CounterComponent;

React click specific element in setInterval loop

I'm trying to click my element in setInterval loop, so it would be clicked every 10 second, but there's always error click is not a function or cannot read click null
I've tired with useRef and also did nothing.
here is my code:
useEffect(() => {
setInterval(function () {
const handleChangeState = () => {
console.log("Now");
document.getElementById("dice").click();
};
handleChangeState();
}, 10 * 1000);
}, []);
return (
<>
<Dice id="dice" rollingTime="3000" triggers={["click", "P"]} />
</>
);
};
It is often considered anti-pattern in React to query the DOM. You should instead use a React ref to gain access to the underlying DOMNode.
There are a couple ways to use a React ref to invoke a dice roll of the child component. FYI, rollingTime should probably be number type instead of a string if using in any setTimeout calls.
Forward the React ref and attach to the button element and invoke the click handler.
Example:
const Dice = forwardRef(({ id, rollingTime }, ref) => {
const timerRef = useRef();
const [value, setValue] = useState();
const [isRolling, setIsRolling] = useState();
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
const roll = () => {
if (!isRolling) {
setIsRolling(true);
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setValue(Math.floor(Math.random() * 6) + 1);
setIsRolling(false);
}, rollingTime);
}
};
return (
<>
<h1>Dice</h1>
<h2>Roll Value: {isRolling ? "Rolling..." : value}</h2>
<button ref={ref} id={id} type="button" onClick={roll}>
Roll the dice
</button>
</>
);
});
...
export default function App() {
const diceRef = useRef();
useEffect(() => {
const handleChangeState = () => {
console.log("Clicking Dice");
diceRef.current?.click();
};
setInterval(() => {
handleChangeState();
}, 10 * 1000);
}, []);
return (
<div className="App">
<Dice
ref={diceRef}
id="dice"
rollingTime={3000}
triggers={["click", "P"]}
/>
</div>
);
}
Forward the React ref and invoke the button's callback function directly via the useImperativeHandle hook.
Example:
const Dice = forwardRef(({ id, rollingTime }, ref) => {
const timerRef = useRef();
const [value, setValue] = useState();
const [isRolling, setIsRolling] = useState();
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
const roll = () => {
if (!isRolling) {
setIsRolling(true);
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setValue(Math.floor(Math.random() * 6) + 1);
setIsRolling(false);
}, rollingTime);
}
};
useImperativeHandle(ref, () => ({
roll
}));
return (
<>
<h1>Dice 2</h1>
<h2>Roll Value: {isRolling ? "Rolling..." : value}</h2>
<button id={id} type="button" onClick={roll}>
Roll the dice
</button>
</>
);
});
...
export default function App() {
const diceRef = useRef();
useEffect(() => {
const handleRollDice = () => {
console.log("Roll dice");
diceRef.current.roll();
};
setInterval(() => {
handleRollDice();
}, 10 * 1000);
}, []);
return (
<div className="App">
<Dice
ref={diceRef}
id="dice"
rollingTime={3000}
triggers={["click", "P"]}
/>
</div>
);
}
Using react-dice-roll
If you examine the react-dice-roll source code you'll see that the Dice component forwards a React ref and uses the useImperativeHandle hook to expose out a rollDice function.
Dice Source
const Dice = forwardRef((props: TProps, ref: React.MutableRefObject<TDiceRef>) => {
...
const handleDiceRoll = (value?: TValue) => {
let diceAudio: HTMLAudioElement;
if (sound) {
diceAudio = new Audio(sound);
diceAudio.play();
}
setRolling(true);
setTimeout(() => {
let rollValue = Math.floor((Math.random() * 6) + 1) as TValue;
if (value) rollValue = value;
if (cheatValue) rollValue = cheatValue;
setRolling(false);
setValue(rollValue);
if (diceAudio) diceAudio.pause();
if (!onRoll) return;
onRoll(rollValue);
}, rollingTime);
};
useImperativeHandle(ref, () => ({ rollDice: handleDiceRoll }));
...
return (
...
)
});
Your code then just needs to create a React ref and pass it to the Dice component, and instantiate the interval in a mounting useEffect hook.
Example:
function App() {
const diceRef = useRef();
useEffect(() => {
const rollDice = () => {
console.log("Rolling Dice");
diceRef.current.rollDice(); // <-- call rollDice function
};
// instantiate interval
setInterval(() => {
rollDice();
}, 10 * 1000);
// immediately invoke so we don't wait 10 seconds for first roll
rollDice();
}, []);
return (
<div className="App">
<Dice
ref={diceRef}
id="dice"
rollingTime={3000}
triggers={["click", "P"]}
/>
</div>
);
}

Why is the min state getting updated 2 times instead of only once?

Why is the min state getting updated in the multiples of two instead of just updating by one after every 59 seconds? How do I fix it?
import { useRef, useState } from "react";
export const Timer = () => {
const [second, setSecond] = useState(0);
const [min, setMin] = useState(0);
const watch = useRef(null);
const startTimer = () => {
watch.current = setInterval(() => {
setSecond((value) => {
if (value === 59) {
setSecond(0);
setMin((v) => v + 1);
}
return value + 1;
});
}, 1000);
};
return (
<div>
<h1>
{min}:{second}{" "}
</h1>
<button onClick={startTimer}>Start</button>
<button onClick={() => clearInterval(watch.current)}>Pause</button>
<button
onClick={() => {
setSecond(0);
return clearInterval(watch.current);
}}
>
Reset
</button>
</div>
);
};
This is the component as a whole. I am new to react so please help.

fetch data is updated but array and state is not updated

i am woking on weather api and storing perticular data in an array arr but value is not available in arr. also state arrdata is null too.
i tried to not use state but still not getting data in arr . it show reading undefined value.
export default function App() {
const [cityName, setCityName] = useState("delhi");
const [arrData, setArrData] = useState(null);
const getWeatherInfo = async () => {
const url = "https://api.openweathermap.org/data/2.5/forecast";
const api = "4beffc863037e89f0f181d893d1cf79b";
fetch(`${url}?q=${cityName}&units=metric&appid=${api}`)
.then((res) => res.json())
.then((getData) => {
if(getData.list[4].main !== null){
const arr = [];
for (let i = 0; i <= 40; i++) {
if (i % 8 === 0) {
arr.push({
temprature: getData.list[i].main.temp,
Min_temp: getData.list[i].main.temp_min,
Max_temp: getData.list[i].main.temp_max,
date: getData.list[i].dt_txt,
mood: getData.list[i].weather[0].main,
weathermoodIcon: getData.list[i].weather[0].icon,
Humidity: getData.list[i].main.humidity,
});
}}
setArrData(arr);
}});
};
useEffect(() => {
getWeatherInfo()
}, []);
console.log(arrData)
const onInputChange = (e) => {
setCityName(e.target.value);
};
const onSubmitCity = () => {
getWeatherInfo();
};
return (
<>
<Input onChangeValue={onInputChange} onSubmit={onSubmitCity} />
</>
);
}
This seems to be working. Please do not forget to use optional chaining
import {useState, useEffect } from 'react';
export default function App() {
const [cityName, setCityName] = useState("delhi");
const [arrData, setArrData] = useState(null);
const getWeatherInfo = async () => {
const url = "https://api.openweathermap.org/data/2.5/forecast";
const api = "4beffc863037e89f0f181d893d1cf79b";
fetch(`${url}?q=${cityName}&units=metric&appid=${api}`)
.then((res) => res.json())
.then((getData) => {
if(getData.list[40]?.main !== null){
const arr = [];
console.log(getData.list)
for (let i = 0; i <= 4; i++) {
if (i % 8 === 0) {
arr.push({
temprature: getData.list[i]?.main.temp,
Min_temp: getData.list[i]?.main.temp_min,
Max_temp: getData.list[i]?.main.temp_max,
date: getData.list[i]?.dt_txt,
mood: getData.list[i]?.weather[0].main,
weathermoodIcon: getData.list[i]?.weather[0].icon,
Humidity: getData.list[i]?.main.humidity,
});
}}
setArrData(arr);
}});
};
useEffect(() => {
getWeatherInfo();
}, []);
console.log(arrData)
const onInputChange = (e) => {
setCityName(e.target.value);
};
const onSubmitCity = () => {
getWeatherInfo();
};
return (
<>
<input onChange={onInputChange} onSubmit={onSubmitCity} />
<h1> {JSON.stringify(arrData)} </h1>
<button onClick = {onSubmitCity}> Submit </button>
</>
);
}

Why React hook useState don't increment index onClick?

I don't understand why in setProjectIndex inside SwitchProject function not updating my projectIndex state :
const WorkPreview = ({project, projects}) => {
const [currProject, setCurrProject] = useState(projects[0]);
const [projectIndex, setProjectIndex] = useState(0);
useEffect(() => {
console.log("useEffect idx", projectIndex) // log: 1 when onClick to Right Button
}, [projectIndex])
const SwitchProject = (incrDecrAmount) => {
let nextIndex = projectIndex + incrDecrAmount;
if (nextIndex >= 0 && nextIndex < (projects.length-1)) {
setProjectIndex(projectIndex + incrDecrAmount); // sets 0
console.log("projectIndex", projectIndex) // log: 0 when onClick to Right Button (increment by 1)
console.log("nextIdx", nextIndex) // log: 1 when onClick to Right Button
setCurrProject(projects[projectIndex]);
console.log("", projects[projectIndex]); // gives projects[0] not projects[1]
}
}
return (
<div className="works__preview" id="workPreview">
<button className="works__itemBtn" id="btnfixedLeft" onClick={() => { SwitchProject(-1) }}>
<Icon.ArrowLeft></Icon.ArrowLeft>
</button>
<button className="works__itemBtn" id="btnfixedRight" onClick={() => { SwitchProject(1) }}>
<Icon.ArrowRight></Icon.ArrowRight>
</button>
</div>
)
I've checked other questions and try different ways but gives the same result
Can someone explain me that and gives me the solution ?
Since you are just increasing/decreasing values, in your position I would just utilize a userReducer hook
import React, { useState, useEffect, useReducer } from "react";
const useCounterHook = (state: { count: number }, action: { type: string }) => {
switch (action.type) {
case "increment":
return { count: ++state.count };
case "decrement":
return { count: --state.count };
default:
throw new Error();
}
};
const WorkPreview = ({ project, projects }) => {
const [currProject, setCurrProject] = useState(projects[0]);
const [state, dispatch] = useReducer(useCounterHook, { count: 0 });
useEffect(() => {
console.log("state effect", state.count);
console.log("state next effect", state.count + 1);
setCurrProject(projects[state.count < 0 ? 0 : state.count]);
}, [projects, state]);
useEffect(() => {
console.log("currProject", currProject);
}, [currProject]);
return (
<div className="works__preview" id="workPreview">
<button
className="works__itemBtn"
id="btnfixedLeft"
onClick={() => dispatch({ type: "decrement" })}
>
<Icon.ArrowLeft></Icon.ArrowLeft>
</button>
<button
className="works__itemBtn"
id="btnfixedRight"
onClick={() => dispatch({ type: "increment" })}
>
<Icon.ArrowRight></Icon.ArrowRight>
</button>
</div>
);
};

Resources