I have an Issue when getting data from a Reducer.
The Issue I believe it is with the Async Code of the Data.
Here is my Code:
import React, { useState, useEffect, useCallback } from "react";
import { View, Text, StyleSheet, Alert, ActivityIndicator, Button } from "react-native";
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import Colors from '../constants/Colors';
import { useSelector, useDispatch } from 'react-redux';
import * as boletoActions from '../store/actions/boletos';
import HeaderButton from '../components/UI/HeaderButton';
const Boleto = (props) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const dispatch = useDispatch();
const userId = useSelector(state => state.auth.userId);
const gameId = useSelector(state => state.boleto.currentGameId);
const loadBoleto = useCallback(async (userId, gameId) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(boletoActions.getTicket(userId, gameId));
} catch (err) {
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setLoading, setError]);
useEffect(() => {
const unsubscribeGuardados = props.navigation.addListener('focus', loadBoleto);
return () => {
unsubscribeGuardados();
};
}, [loadBoleto]);
useEffect(() => {
setLoading(true);
loadBoleto(userId, gameId).then(() => {
setLoading(false);
});
}, [dispatch, loadBoleto]);
const boletoInfo = useSelector(state => state.boleto.ticket);
var codigo = boletoInfo.length != 0 ? boletoInfo[0].id : '';
var cantidad = boletoInfo.length != 0 ? boletoInfo[0].cantidad : '';
var localidad = boletoInfo.length != 0 ? boletoInfo[0].nombreLocalidad : '';
var local = boletoInfo.length != 0 ? boletoInfo[0].local : '';
var visitante = boletoInfo.length != 0 ? boletoInfo[0].visita : '';
var logo = boletoInfo.length != 0 ? boletoInfo[0].logo : '';
var usado = boletoInfo.length != 0 ? boletoInfo[0].usado : '';
if (loading) {
return <View style={styles.centered} >
<ActivityIndicator size='large' color={Colors.primary} />
</View>
}
if (!error === null) {
return <View style={styles.centered} >
<Text style={styles.errorText}>No se pudo Cargar el Ticket</Text>
<Button title='Recargar' onPress={loadBoleto} color={Colors.secondary} />
</View>
}
return (
<View style={styles.centered}>
<Text style={styles.title}>Codigo Transaccion: <Text style={styles.title2}>{codigo}</Text></Text>
<Text style={styles.title}>Cantidad: <Text style={styles.title2}>{cantidad}</Text></Text>
<Text style={styles.title}>Localidad: <Text style={styles.title2}>{localidad}</Text></Text>
<Text style={styles.title}>Local: <Text style={styles.title2}>{local}</Text></Text>
<Text style={styles.title}>Visitante: <Text style={styles.title2}>{visitante}</Text></Text>
<Text style={styles.title}>logo: <Text style={styles.title2}>{logo}</Text></Text>
</View>
);
};
export const screenOptions = navData => {
return {
headerTitle: 'Boleto',
headerLeft: () => (<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Back'
iconName={Platform.OS === 'android' ? 'md-arrow-back-outline' : 'ios-arrow-back-outline'}
onPress={() => {
navData.navigation.navigate('Noticias');
}} />
</HeaderButtons>)
}
};
const styles = StyleSheet.create({
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
marginTop: 15,
marginBottom: 5,
fontSize: 20,
color: 'black',
alignContent: 'center',
fontWeight: 'bold',
},
title2: {
marginTop: 15,
marginBottom: 5,
fontSize: 20,
color: Colors.primary,
alignContent: 'center',
fontWeight: 'bold',
},
errorText: {
marginTop: 15,
marginBottom: 5,
fontSize: 20,
color: Colors.secondary,
alignContent: 'center',
fontWeight: 'bold',
},
});
export default Boleto;
Now the first time I access I see this:
I think this is because on loading it boletoInfo is Length less than 0, however if I add a console.log to this Page, it shows the info.
I added in that picture the line console.log('x');
And then I see this:
I need to display the info as soon as I Load the page.
Any Ideas?
You want to make sure that the ticket exists before you attempt to render it. Is it possible that all of the actions complete, but the user doesn't have a ticket to this game?
Instead of falling back to empty strings, you should avoid that section entirely if there is not ticket. I'm moving that code block to a separate component that we can call.
// only render this component when you know that you have a ticket
export const RenderBoleto = ({boleto}) => {
return (
<View style={styles.centered}>
<Text style={styles.title}>
Codigo Transaccion: <Text style={styles.title2}>{boleto.id}</Text>
</Text>
<Text style={styles.title}>
Cantidad: <Text style={styles.title2}>{boleto.cantidad}</Text>
</Text>
<Text style={styles.title}>
Localidad: <Text style={styles.title2}>{boleto.nombreLocalidad}</Text>
</Text>
<Text style={styles.title}>
Local: <Text style={styles.title2}>{boleto.local}</Text>
</Text>
<Text style={styles.title}>
Visitante: <Text style={styles.title2}>{boleto.visita}</Text>
</Text>
<Text style={styles.title}>
logo: <Text style={styles.title2}>{boleto.logo}</Text>
</Text>
</View>
);
}
You have defined the callback loadBoleto
as a function that takes arguments userId
and gameId
. But there are some places where you are calling it that don't make sense. When it's called through the onPress
of a Button
or a listener of props.navigation
then the argument will be the event -- not the userId
and gameId
.
You could change your current function or create a wrapper function that calls it. This function takes no arguments because it uses the userId
and gameId
from the selectors. But it needs to have them as dependencies.
// defines a callback that loads a ticket for THIS CURRENT userId and gameId
const loadCurrentBoleto = useCallback(
async () => {
loadBoleto(userId, gameId);
},
[loadBoleto, userId, gameId]
)
We can create a variable for the first element of the boletoInfo
array
. If this boleto
variable doesn't exist then we should show the error screen, even if the action completed without an error. I'm setting the initial value of loading
to true
just to make sure that the very first thing we see is loading screen.
const Boleto = (props) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const dispatch = useDispatch();
const userId = useSelector((state) => state.auth.userId);
const gameId = useSelector((state) => state.boleto.currentGameId);
// returns an array
const boletoInfo = useSelector((state) => state.boleto.ticket);
// is either a ticket object or undefined depending on the length of the array
const boleto = boletoInfo[0];
// defines a callback that loads a ticket for THIS CURRENT userId and gameId
const loadCurrentBoleto = useCallback(
async () => {
setError(null);
setLoading(true);
try {
await dispatch(boletoActions.getTicket(userId, gameId));
} catch (err) {
setError(err.message);
}
setLoading(false);
},
[dispatch, setLoading, setError, userId, gameId]
)
// need to use loadCurrentBoleto here instead of loadBoleto
useEffect(() => {
const unsubscribeGuardados = props.navigation.addListener(
"focus",
loadCurrentBoleto
);
return () => {
unsubscribeGuardados();
};
}, [loadCurrentBoleto]);
// load ticket automatically
useEffect(() => {
// can we skip this is boleto is defined?
loadCurrentBoleto();
}, [dispatch, loadCurrentBoleto]);
if (loading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colors.primary} />
</View>
);
}
if (boleto) {
return (
<RenderBoleto boleto={boleto}/>
)
}
// show error screen if boleto undefined
return (
<View style={styles.centered}>
<Text style={styles.errorText}>No se pudo Cargar el Ticket</Text>
<Button
title="Recargar"
onPress={loadCurrentBoleto}
color={Colors.secondary}
/>
</View>
);
};