Work with Immutable.JS
cristian-sima opened this issue · comments
It seems that inside the templates (doesn't matter which one), there are injected the prevState
and curState
, but these are serialized. In the case I have some Immutable JS objects, they will be transformed to plain JS.
A solution will be to be given the original state (originalPrevState
and originalCurState
) which are the Immutable objects
Could you provide an example template you'd use with those objects?
Thanks for looking into it!
Sure, but I'll give it tomorrow cause it's too late now
There's no hurry. Anyway, in the current implementation it's not possible to have the original objects as they are stringified before sending to the extension. But I'm working on another way of sending data.
@zalmoxisus Sorry for delay.
Essentially, it is pretty simple. For any example which includes Immutable.JS anywhere in the action or state, redux-devtools-test-generator
will produce code which breaks.
In this example I normalize some invoices. I use 2 reducers:
all
stores their IDs as anImmutable.List
byIDs
stores their content as aImmutable.Map
The companyInvoices
reducer just stores the all
and byIDs
reducers.
For simplicity, I react to just one action: COMPANY_ADD_INVOICE
.
import { combineReducers } from "redux";
import * as Immutable from "immutable";
const COMPANY_ADD_INVOICE = "COMPANY_ADD_INVOICE";
return combineReducers({
companyInvoices: combineReducers({
all: (state = Immutable.List(), action) => {
const { payload } = action;
switch (action.type) {
case COMPANY_ADD_INVOICE: {
const invoice = payload,
id = String(invoice.get("ID"));
return {
...state,
IDs: state.IDs.push(id),
};
}
default:
return state;
}
},
byIDs: (state = Immutable.Map(), action) => {
const { payload } = action;
switch (action.type) {
case COMPANY_ADD_INVOICE: {
const invoice = payload;
return state.set(String(invoice.get("ID")), invoice);
}
default:
return state;
}
},
}),
});
const initialState = {
companyInvoices: {
all : Immutable.List(),
byIDs : Immutable.Map(),
},
};
// now I dispatch an action
dispatch({
type : COMPANY_ADD_INVOICE,
payload : Immutable.Map({
ID : 1,
Description : "T.V. Subscription",
Price : 49.99,
}),
});
const afterState= {
companyInvoices: {
all : Immutable.List(["1"]),
byIDs : Immutable.Map({
"1": Immutable.Map({
ID : 1,
Description : "T.V. Subscription",
Price : 49.99,
}),
}),
},
};
When using redux-devtools-test-generator
the state will be serialized into:
const initialState = {
companyInvoices: {
all : [],
byIDs : {},
},
};
dispatch({
type : COMPANY_ADD_INVOICE,
payload : {
ID : 1,
Description : "T.V. Subscription",
Price : 49.99,
},
});
const afterState = {
companyInvoices: {
all : ["1"],
byIDs : {
1: {
ID : 1,
Description : "T.V. Subscription",
Price : 49.99,
},
},
},
};
which is inserted into Jest
template
import reducers from "../../reducers";
test("reducers", () => {
let state;
state = reducers({
companyInvoices: {
all : [],
byIDs : {},
},
}, {
type : "COMPANY_ADD_INVOICE",
payload : {
ID : 1,
Description : "T.V. Subscription",
Price : 49.99,
},
});
expect(state).toEqual({
companyInvoices: {
all : ["1"],
byIDs : {
1: {
ID : 1,
Description : "T.V. Subscription",
Price : 49.99,
},
},
},
});
});
which breaks everything because:
action
is deserialed and then when doingpayload.get("ID")
we'll get anpayload.get is not a function
error- initial state is not the same as the initial state deserialed
- after state is not the same as the after state deserialed
An approach is to provide originalAction
, originalInitialState
and originalAfterState
.
This example is pretty simple, however my apps are structured like this for everything
@cristian-sima, thanks for the details!
I still don't see how providing originalAction
, originalInitialState
and originalAfterState
could help to have a better template.
A solution would be to add fromJS
and toJS
in the template, but of course it requires that everything including the action object would be Immutable.
Another solution is to indicate how to serialize the data using serializeAction
and serializeState
parameters. For example:
const customSerializer = (key, value) => (
value && Map.isMap(value) ? `Map(${value.toJS()})` : value
);
const store = Redux.createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
serializeState: customSerializer,
serializeAction: customSerializer,
}));
So, instead of serializing maps into plain objects like { a: 1 }
, you'll have Map({ a: 1 })
. You can add other types or even use iterables instead of plain objects.
Sorry again for the great delay. I'll answer soon :D