0

I'm using Redux and ImmutableJS to manage the state of my app. I've created the following two Records:

export const OrderRecord = Record({
    id: null,
    productId: null,
    amount: 1,
});

export const ProductRecord = Record({
    id: null,
    name: '',
    price: 0,
});

My global state is normalized based on the normalizr approach like this:

const state = {
    entities: {
        orders: new OrderedMap(new Map({
            1: new OrderRecord(createOrderItem(1, 1)),
        })),
        products: new OrderedMap(new Map({
            1: new ProductRecord(createProductItem(1)),
        })),
    },
};
  • I'm using this specification for testing purposes.

Now I'm trying to make some selects with computed fields using Reselect.

export const getVisibleOrders = createSelector(
    [getProducts, getOrders],
    (products, orders) => {
        orders.map(order => {
            const product = products.get(order.productId.toString());
            if (!product) {
                return order;
            }
            const totalPrice = order.amount * product.price;
            order.set('productName', product.name);
            order.set('totalPrice', totalPrice);
            return order;
        });
    }
); 

, but I get the following error message:

Error: Cannot set unknown key "productName" on Record

I know the reason - Record cannot contain any undefined keys, but my question is: Is there any suggested approach how gracefully solved this problem?

  • I don't want to extend my Records to support this kind of computed parameters (product.name and totalPrice).
  • I don't want to keep the static and computed parameters in one place, because for example the 'productName' parametr is from "Product" entity and not from "Order" entity.

Thank you.

jezikk
  • 111
  • 2
  • 9

1 Answers1

1

The whole point of using Immutable.Record is to not let you add new keys to your record, hence the error message you get. And the whole point of selectors is to expose such "computed" property if you want to consume them outside. In your case, you can simply return a new Map() object or a new record type if you need to use the dotted syntax :

return Map({
  productName: 'foobar'
}).merge(order)
Pierre Criulanscy
  • 8,726
  • 3
  • 24
  • 38
  • Thank you, It's work for me, but the whole function "getVisibleOrders" is returning "undefined" value now. – jezikk Jul 30 '16 at 14:32
  • Yes because you need to use parenthesis instead of brackets : `(products, orders) => ( ... )` not `(products, orders) => { ... }`. In fact parenthesis are optionnal here since there is only one statement after the fat arrow. If you want to keep brackets, you need to explicitly return the result of the `orders.map()` statement : `return orders.map(....)`. – Pierre Criulanscy Jul 30 '16 at 14:36
  • Oka, thanks. After modification the output is this `{ '1': [Function: Record] }`. – jezikk Jul 30 '16 at 14:54
  • It seems like `new Record({})` doesn't work as expected. – jezikk Jul 30 '16 at 16:10
  • Yep sorry I was missing the parenthesis, `Record` is effectively a function you need to call in order to get back the result. You can remove your answer then :) – Pierre Criulanscy Jul 30 '16 at 16:19
  • I'm sorry, but this doesn't work too :( - `Error: Cannot set on an immutable record.` – jezikk Jul 30 '16 at 17:43
  • My bad, it should work with the "new" statement (I edited my answer) – Pierre Criulanscy Jul 30 '16 at 17:46
  • In fact, that should work without the `new` keyword, I tried in a jsfiddle and it works, which line triggers the error ? – Pierre Criulanscy Jul 30 '16 at 18:38
  • It's works without `new` keyword, but only without `...order,`. If I add `...order,` into constructor, error message `Error: Cannot set on an immutable record` is raised. – jezikk Jul 30 '16 at 18:47
  • Yeah, it seems that `Record` does not support well the destructuring syntax. Do you absolutly need to return a Record ? – Pierre Criulanscy Jul 30 '16 at 19:04
  • No, it could be `Map` for example, but `Map` doesn't work for me too. – jezikk Jul 30 '16 at 20:12
  • Ok, so this one works for me `return Record({ ...order.toObject(), productName, totalPrice, })();` – jezikk Jul 30 '16 at 20:32