This might the same scenario which has been documented here Resetting all state when a prop changes
Discussing below some points relevant to this question.
Now coming to this question:
Now this post is asking for a solution to avoid the stale or outdated render which always happens prior to the useEffect, the very same point we have discussed above in point 7.
Some background information of a possible solution
Please note that components will preserve state between renders. It means normally when a function object terminates its invocation, all variables declared inside the function object will be lost.
However React functional object has the ability to retain state values between renders. The default state retention rule of React is that, it will retain the state as longs as the same component renders in the same position in the UI Render tree. For more about this can be read here Preserving and Resetting State.
Though the default behaviour will suiting in most use-cases, and therefore it has become the default behaviour of React, the context which we are now in does not suit to this behaviour. We do not want React to retain the previous fetch.
It means we want a way to tell react that please reset the state along with the change in the props. Please note that even if we are success to reset the state, the render process and useEffect are still going to run in the same normal order. There will be an initial render with the latest state and a useEffect run as the follow up of render. However the improvement we may achieve here is that this initial render will be with the initial value which we have passed into useState. Since this initial is a fixed value, always, we can use it to build a conditional rendering of the two state values - Loading status or the fetched data.
The following two sample codes, demo the same.
The first code below, demoes the issue we have been discussing.
app.js
import { useEffect, useState } from 'react';
export default function App() {
return <Page />;
}
function Page() {
const [contentId, setContentId] = useState(Math.random());
return (
<>
<Content contentId={contentId} />
<br />
<button onClick={() => setContentId(Math.random())}>
Change contentId
</button>
</>
);
}
function Content({ contentId }) {
const [mockData, setMockData] = useState(null);
useEffect(() => {
new Promise((resolve) => {
setTimeout(() => {
resolve(`some mock data 1,2,3,4.... for ${contentId}`);
}, 2000);
}).then((data) => setMockData(data));
}, [contentId]);
return <>mock data : {mockData ? mockData : 'Loading data..'}</>;
}
Test run
Test plan : Clicking the button to change contentId
The UI Before clicking
The UI for less than 2 seconds, just after clicking
Observation
The previous data retained for less than 2 seconds, this is not the desired UI. The UI should change to inform user that data loading is going on. And upon 2 seconds, the mock data should come into the UI.
The second code below, addresses the issue.
It addresses the issue by using the property key. This property has great significance in the state retention scheme.
In brief, what happens now is that, React will reset the state if the key changes between two renders.
App.js
import { useEffect, useState } from 'react';
export default function App() {
return <Page />;
}
function Page() {
const [contentId, setContentId] = useState(Math.random());
return (
<>
<Content contentId={contentId} key={contentId} />
<br />
<button onClick={() => setContentId(Math.random())}>
Change contentId
</button>
</>
);
}
function Content({ contentId }) {
const [mockData, setMockData] = useState(null);
useEffect(() => {
new Promise((resolve) => {
setTimeout(() => {
resolve(`some mock data 1,2,3,4.... for ${contentId}`);
}, 2000);
}).then((data) => setMockData(data));
}, [contentId]);
return <>mock data : {mockData ? mockData : 'Loading data..'}</>;
}
Test run
Test plan : Clicking the button to change contentId
The UI before clicking
The UI for less than 2 seconds, after clicking
The UI after seconds
Observation
The previous data did not retain, instead the IU displayed the loading status and updated it as soon as the actual data had come. This may be the UI desired in this use-case.
Citation
How does React determine which state to apply when children are conditionally rendered?