const { Fragment, StrictMode, useRef, useState } = React;
const { createRoot } = ReactDOM;
function CountLabel({ count }) {
const prevCount = useRef(count);
console.log("prevCount: ", prevCount.current)
console.log("count: ", count)
const [trend, setTrend] = useState(null);
if (prevCount.current !== count) {
setTrend(count > prevCount.current ? 'increasing' : 'decreasing');
prevCount.current = count;
}
console.log("trend: ", trend)
return (
<Fragment>
<h1>{count}</h1>
{trend && <p>The count is {trend}</p>}
</Fragment>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<Fragment>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<CountLabel count={count} />
</Fragment>
);
}
const strictModeEnabled = createRoot(document.getElementById("strict-mode-enabled"));
strictModeEnabled.render(
<StrictMode>
<h1>Strict Mode Enabled</h1>
<App />
</StrictMode>
);
const strictModeDisabled = createRoot(document.getElementById("strict-mode-disabled"));
strictModeDisabled.render(
<Fragment>
<h1>Strict Mode Disabled</h1>
<App />
</Fragment>
);
#wrapper {
display: flex;
}
#wrapper > div {
margin: 16px;
}
<div id="wrapper">
<div id="strict-mode-enabled"></div>
<div id="strict-mode-disabled"></div>
</div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
The expectation is on click of Increment, the count should increment by 1 and trend should be increasing and opposite for on click of Decrement.
In strict and development mode, the count is increasing, but the trend is always null
.
I know in strict mode React is calling this child component two times.
During first render, the logs are:
prevCount: 0
count: 0
trend: null
prevCount: 0
count: 0
trend: null
After Increment click, the logs are:
prevCount: 0
count: 1
trend: null
prevCount: 1
count: 1
trend: increasing
prevCount: 1
count: 1
trend: null
But if strict mode is off, it works fine as expected. It also works fine if instead of useRef
, we keep prevCount
in state like below:
const { Fragment, StrictMode, useRef, useState } = React;
const { createRoot } = ReactDOM;
function CountLabel({ count }) {
const [prevCount, setPrevCount] = useState(count);
const [trend, setTrend] = useState(null);
console.log("prevCount: ", prevCount)
console.log("count: ", count)
if (prevCount !== count) {
setPrevCount(count);
setTrend(count > prevCount ? 'increasing' : 'decreasing');
}
console.log("trend: ", trend)
return (
<Fragment>
<h1>{count}</h1>
{trend && <p>The count is {trend}</p>}
</Fragment>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<Fragment>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<CountLabel count={count} />
</Fragment>
);
}
const strictModeEnabled = createRoot(document.getElementById("strict-mode-enabled"));
strictModeEnabled.render(
<StrictMode>
<h1>Strict Mode Enabled</h1>
<App />
</StrictMode>
);
const strictModeDisabled = createRoot(document.getElementById("strict-mode-disabled"));
strictModeDisabled.render(
<Fragment>
<h1>Strict Mode Disabled</h1>
<App />
</Fragment>
);
#wrapper {
display: flex;
}
#wrapper > div {
margin: 16px;
}
<div id="wrapper">
<div id="strict-mode-enabled"></div>
<div id="strict-mode-disabled"></div>
</div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
The logs on first render is same as above, but this is how the logs are on click of increment button:
prevCount: 0
count: 1
trend: null
prevCount: 1
count: 1
trend: increasing
prevCount: 0
count: 1
trend: null
prevCount: 1
count: 1
trend: increasing
My doubt is, why using useRef
is bad idea here? Works good in non-strict mode, but not good in strict mode?