Reputation: 243
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
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