It's not really "async". count is a constant for that render. setCount schedules the next render with a new value, but your function keeps seeing the old one. setTimeout just papers over it.
Compute the next value once and use it:
function add() {
const next = count + 1;
setCount(next);
if (next >= 5) showLimitWarning();
}Keep side effects like the warning in the event handler, where you know why it changed. A useEffect on count fires for every reason count changes, which is exactly your page-load problem.