In this React interview problem, you are asked to:
We want to create a red button.
When you click the button, your points increase by 1.
You can buy a blue factory for 5 points.
For each factory you have, your points increase by
N each 1 second on an interval,
where N is the number of factories you have.
So if you have 2 factories, your points should increase
by 2 every 1 second.
import { useEffect, useRef, useState } from "react";
import "./App.css";
function App() {
const [points, setPoints] = useState(0);
const [numFactories, setNumFactories] = useState(0);
const intervalRef = useRef<number | null>(null);
const numFactoriesRef = useRef(numFactories);
useEffect(() => {
numFactoriesRef.current = numFactories;
}, [numFactories]);
useEffect(() => {
if (intervalRef.current) {
return;
}
const intervalId = setInterval(() => {
setPoints((prev) => prev + numFactoriesRef.current);
}, 1000);
intervalRef.current = intervalId;
}, []);
const handleBuyFactory = () => {
if (points >= 5) {
setPoints((prev) => {
return prev - 5;
});
setNumFactories(numFactories + 1);
}
};
return (
<>
<p>{`${points} points`}</p>
<div className="circle" onClick={() => setPoints(points + 1)}></div>
<button onClick={handleBuyFactory}>buy factory - 5 pts</button>
<div className="factories">
{new Array(numFactories).fill(0).map((_, idx) => (
<div key={idx} className="factory"></div>
))}
</div>
</>
);
}
}
Why this works:
We ideally only want to ever create a single interval which should run in the background of the program.
By having a single interval, we come as close as possible in React world to satisfying the constraint that some action should happen every second (or as close as possible to every second).
Here, we store a reference to the interval (which should be treated as a singleton) in a ref, so that we can later check to ensure whether we have already created our single interval.
const intervalRef = useRef<number | null>(null);
const intervalId = setInterval(() => {
setPoints((prev) => prev + numFactoriesRef.current);
}, 1000);
intervalRef.current = intervalId;
We store the number of points and number of factories as primitive numbers in state. We also store the number of factories in a ref. Each time the number of factories in state is updated, we update the number of factories stored in the ref.
This is because when we initially create the interval and it gets stored in memory, it will retain (in a closure) the values of any variables which it references.
So in the above implementation, if we had referenced numFactories inside of the callback function in setPoints, rather than referencing numFactoriesRef.current, we would have seen the initial value of numFactories - 0 - used here - even on subsequent iterations of the interval loop. However, setPoints always needs the most up-to-date value of numFactories. Thus, we continuously store the value of numFactories inside a ref (so this value can persist across re-renders.)