I'm trying to render a component from my screen after 3 seconds, this is due to some time delay when the user changes accounts, I need those 3 seconds so the data can be exactly what the user wants. Before rendering that piece of component, I wanted to load a container with loading in the center. I have inserted the data into the component but I get an error from component exception
:
This is how I'm rendering the data:
export function Dashboard({ drawerAnimationStyle}: any) {
const navigation: any = useNavigation();
const { firebaseFunctions } = useAuth();
const [isLoading, setIsLoading] = useState(true);
const [transactions, setTransactions] = useState<DataListProps[]>([]);
const [higlightData, setHighlightData] = useState<HighlightDataProps>({} as HighlightDataProps);
const theme = useTheme();
async function loadTransaction(): Promise<any> {
const data = await firebaseFunctions.loadTransaction();
setTransactions(data.transactionsFormatted);
setHighlightData(data.higlightData);
setIsLoading(false);
}
useEffect(() => {
loadTransaction();
}, [transactions]);
return (
<Animated.View style={{ flex: 1, ...drawerAnimationStyle}}>
<Container>
{
isLoading ?
<LoadingContainer>
<ActivityIndicator color={theme.colors.blue} size='large'/>
</LoadingContainer>
:
<>
<Header>
<DrawerContainer onPress={() => navigation.openDrawer()}>
<Icon name="menu" />
</DrawerContainer>
<HighLightCards>
<HighLightCard type="up" title="Income" amount={higlightData.entries.amount} lastTransaction={higlightData.entries.lastTransaction}/>
<HighLightCard type="down"title="Outcome" amount={higlightData.outcome.amount}lastTransaction={higlightData.outcome.lastTransaction}/>
<HighLightCard type="total" title="Total" amount={higlightData.total.amount} lastTransaction={higlightData.total.lastTransaction}/>
</HighLightCards>
</Header>
{
setTimeout(() => {
return (
<Transactions>
<Title>Transactions</Title>
{
transactions.length <= 0 ?
<NoTransaction>
<LottieView
source={NoTransactionLottie}
style={{
width: 100,
height: 100,
}}
autoPlay
loop
resizeMode='contain'
/>
</NoTransaction>
:
<TransactionsList
data={transactions}
keyExtractor={(item: { id: any }) => item.id}
renderItem={({ item }: any) => <TransactionCard data={item}/>}
/>
}
</Transactions>
);
}, 3000)
}
</>
}
</Container>
</Animated.View>
);
}
CodePudding user response:
setTimeout
is not valid inside the function return because it doesn't have any implicit return. This is also an anti-pattern in that you will display after 3 seconds whether you have data or not. You should show data if you have it, and indicate loading (or failure) if you don't.
You already have a loading state in the indicator, so if you can't trust that, you should focus on fixing that instead of trying to delay rendering.
If you absolutely must delay rendering for some reason, don't use setTimeout
in the return, use it in the function body. e.g.
...
const [delayComplete, setDelayComplete] = useState(false);
...
useEffect(() => {
if (isLoading || delayComplete) return;
setTimeout(() => {
setDelayComplete(true)
}, 3000);
}, [isLoading, delayComplete, setDelayComplete]);
// in return
{ delayComplete ? (...): (...) }
CodePudding user response:
It's not a good idea to introduce a race condition.
Instead, listen for your async data to be ready and set the loading flag appropriately.
useEffect(() => {
// Or however you decide to check if your state is set
if (transactions.length && Object.keys(higlightData).length) setLoading(false);
}, [transactions, higlightData]);
// then
{loading ? <Loading /> : <StuffThatUsesTransactionsAndHighlightData />}
Also, if loading is not set in any other place, then this is derived state and should be simplified to
const loading = transactions.length && Object.keys(higlightData).length;