Tom
Tom

Reputation: 243

Undefined is not a object - React Navigation

Sorry if this is very long winded, but I have quite a complex error with my React Native project which I just want to explain.

I have two screens, A and B. Screen A performs an API request to get a list of transactions. When you press on a transaction, the app navigates to screen B, and performs another API request to get the details for that transaction. However, on this screen, the following error is returned:

TypeError: undefined is not an object (evaluating 'transactionDetails.length')

The error itself makes sense to me, the transactionDetails object is undefined, although I am not sure why. However, to overcome this, I have tried performing a check to see if the transactionDetails object is undefined, and if it is, skip the .length function. However, this then causes the following error when navigating to screen B:

TypeError: undefined is not an object (evaluating 'transactions.length')

Which is on Screen A, so I have no clue why that page is re-rendering when screen B gets loaded... if that makes any sense.

I can get rid of that error if I perform the same check I added on Screen B to Screen A as well, and then I get no errors. However, if I navigate from Screen A to Screen B, and press the back button, the data from the API on Screen A is no longer there, and can only be retrieved with a pull to refresh.

Note: Screen A and Screen B perform the if check in different places - I have tried having them in the same place and this is not what is causing the error

Sorry for that long explanation, but I hope that makes sense. Here are the relevant sections of code to this app:

Screen A

const ScreenA = (props) => {
    const [isLoading, setIsLoading] = useState();
    const [error, setError] = useState();
    
    const transactions = useSelector(
        (state) => state.transactions.transactionsList
    );

    const dispatch = useDispatch();

    const loadTransactions = useCallback(async () => {
        setError(null);
        setIsLoading(true);
        try {
            await dispatch(transactionActions.fetchTransactions(id));
        } catch (err) {
            setError(err.message);
        }
        setIsLoading(false);
    }, [dispatch, setIsLoading, setError]);

    useEffect(() => {
        setIsLoading(true);
        loadTransactions().then(() => {
            setIsLoading(false);
        });
    }, [dispatch, loadTransactions]);

    return (
        {!isLoading && transactions.length === 0 ? (
            // Error
        ) : (
            // Screen Content
        )}
    );
}

Screen B

const ScreenB = (props) => {
    const [isLoading, setIsLoading] = useState();
    const [error, setError] = useState();

    const transactionDetails = useSelector(
        (state) => state.transactions.transactionDetails
    );

    const dispatch = useDispatch();

    const loadTransactionDetails = useCallback(async () => {
        setError(null);
        setIsLoading(true);
        try {
            await dispatch(transactionActions.fetchTransactionDetails(id));
        } catch (err) {
            setError(err.message);
        }
        setIsLoading(false);
    }, [dispatch, setIsLoading, setError]);

    useEffect(() => {
        setIsLoading(true);
        loadTransactionDetails().then(() => {
            setIsLoading(false);
        });
    }, [dispatch, loadTransactionDetails]);

    /* THIS IS THE BREAKING LINE*/
    if (!isLoading && transactionDetails.length === 0) {
        // Error
    }

    return (
        // Screen content
    );
}

Reducer

const initialState = {
    transactionsList: [] as Transaction[],
    transactionDetails: [] as TransactionDetails[]
};

export default (state = initialState, action) => {
    switch (action.type) {
        case TRANSACTIONS:
            return {
                transactionsList: action.transactions,
            };

        case TRANSACTION_DETAILS:
            return {
                transactionDetails: action.transactionDetails,
            };

        default:
            return state;
    }
};

Debugging Information To help debug, I tried adding a console.log to both screen A and B, just after where transactions and transactionDetails are declared respectivley, to log their value. Screen A prints array[] a couple of times before printing the response data, whereas Screen B prints undefined. I assume this is what the error comes down to, transactionDetails is undefined when empty, rather than array[].

Upvotes: 0

Views: 201

Answers (1)

Shyam
Shyam

Reputation: 5497

The issue is in your reducer ,

export default (state = initialState, action) => {
    switch (action.type) {
        case TRANSACTIONS:
       // you need to spread the existing state
         return { ...state, transactionsList: action.transactions};

        case TRANSACTION_DETAILS:
            return { ...state,transactionDetails: action.transactionDetails };

        default:
            return state;
    }
};

when you dispatch the action of type TRANSACTIONS you are just returning

{
   transactionsList: action.transactions,
};

So when you go to screen B , the state transactions in the store will just have this

{
 // other piece of state
 .... 
transactions: { 
   transactionsList: [....]
}
....
}

There wont be transactionDetails so the below code will return the value undefined .

const transactionDetails = useSelector(
        (state) => state.transactions.transactionDetails
    );

Upvotes: 1

Related Questions