Search code examples
react-nativereduxuse-effect

Error on Loading Data from a Reducer, Issue in await


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:

Image Empty

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.

Added Console Log X

I added in that picture the line console.log('x');

And then I see this:

Info Loaded

I need to display the info as soon as I Load the page.

Any Ideas?


Solution

  • 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>
      );
    };