Reputation: 7755
I'm trying to add a new card in the banking_cards array
inside of the paymentMethods
array. The banking_cards
array is inside the paymentMethods
array. So i wanted to insert the new card object inside the banking_cards
array. My code below produces an error which says state.paymentMethods.banking_cards is not iterable
.
TAKE NOTE
banking_cards
array is inside the paymentMethods
array
export const initialState = {
paymentMethods: [],
};
case paymentMethodConstants.ADD_CARD_SUCCESS:
return {
...state,
paymentMethods: [
...state.paymentMethods,
banking_cards: [...state.paymentMethods.banking_cards, action.payload],
],
};
JSON
paymentMethods = [
{
"id": 8,
"customer_token": "epofjoe",
"banking_cards": [
{
"id": 1,
"banking_token": "AAA",
"last_4": "0006",
"exp_year": 2021,
"exp_month": 12,
"cvc": 876
},
{
"id": 2,
"banking_token": "BBB",
"last_4": "0002",
"exp_year": 2022,
"exp_month": 12,
"cvc": 877
},
]
}
]
Upvotes: 1
Views: 279
Reputation: 39270
When adding a card to a payment method your action needs to contain what payment method the card needs to be added to.
Below is a working example of how you could do that:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const createId = ((id) => () => id++)(3);
const initialState = {
paymentMethods: [
{
id: 8,
banking_cards: [
{
id: 1,
},
{
id: 2,
},
],
},
{
id: 9,
banking_cards: [
{
id: 1,
},
{
id: 2,
},
],
},
],
};
//action types
const ADD_CARD_SUCCESS = 'ADD_CARD_SUCCESS';
//action creators
const addCardSuccess = (payementMethodId, card) => ({
type: ADD_CARD_SUCCESS,
payload: { payementMethodId, card },
});
const reducer = (state, { type, payload }) => {
if (type === ADD_CARD_SUCCESS) {
const { payementMethodId, card } = payload;
return {
...state,
paymentMethods: state.paymentMethods.map((method) =>
method.id === payementMethodId
? {
...method,
banking_cards: [
...method.banking_cards,
card,
],
}
: method
),
};
}
return state;
};
//selectors
const selectPaymentMethods = (state) =>
state.paymentMethods;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
//pure component so not re rendering when nothing changed
const Card = React.memo(function Card({ card }) {
return <li>card: {card.id}</li>;
});
//pure component so it won't re render when nothing changes
const PaymentMethod = React.memo(function PaymentMethod({
paymentMethod,
}) {
const dispatch = useDispatch();
return (
<li>
payment method id: {paymentMethod.id}
<ul>
{paymentMethod.banking_cards.map((card) => (
<Card key={card.id} card={card} />
))}
</ul>
<button
onClick={() =>
dispatch(
//dispatch action with payment method id
addCardSuccess(paymentMethod.id, {
//the new card to be added
id: createId(),
})
)
}
>
Add card
</button>
</li>
);
});
const App = () => {
const paymentMethods = useSelector(selectPaymentMethods);
return (
<ul>
{paymentMethods.map((paymentMethod) => (
<PaymentMethod
key={paymentMethod.id}
paymentMethod={paymentMethod}
/>
))}
</ul>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>
With immer I'd use payment method index and not need the map:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { produce } = immer;
const createId = ((id) => () => id++)(3);
const initialState = {
paymentMethods: [
{
id: 8,
banking_cards: [
{
id: 1,
},
{
id: 2,
},
],
},
{
id: 9,
banking_cards: [
{
id: 1,
},
{
id: 2,
},
],
},
],
};
//action types
const ADD_CARD_SUCCESS = 'ADD_CARD_SUCCESS';
//action creators
const addCardSuccess = (index, card) => ({
type: ADD_CARD_SUCCESS,
payload: { index, card },
});
const reducer = (state, { type, payload }) => {
if (type === ADD_CARD_SUCCESS) {
//lot less hassle using index and immer
const { index, card } = payload;
return produce(state, (draft) => {
draft.paymentMethods[index].banking_cards.push(card);
});
}
return state;
};
//selectors
const selectPaymentMethods = (state) =>
state.paymentMethods;
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
//pure component so not re rendering when nothing changed
const Card = React.memo(function Card({ card }) {
return <li>card: {card.id}</li>;
});
//pure component so it won't re render when nothing changes
const PaymentMethod = React.memo(function PaymentMethod({
paymentMethod,
index,
}) {
const dispatch = useDispatch();
return (
<li>
payment method id: {paymentMethod.id}
<ul>
{paymentMethod.banking_cards.map((card) => (
<Card key={card.id} card={card} />
))}
</ul>
<button
onClick={() =>
dispatch(
//dispatch action with payment method index
addCardSuccess(index, {
//the new card to be added
id: createId(),
})
)
}
>
Add card
</button>
</li>
);
});
const App = () => {
const paymentMethods = useSelector(selectPaymentMethods);
return (
<ul>
{paymentMethods.map((paymentMethod, index) => (
<PaymentMethod
key={paymentMethod.id}
paymentMethod={paymentMethod}
index={index}
/>
))}
</ul>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
Upvotes: 1