Question: Selectors broken after update, arguments are not working.
augustbjornberg opened this issue · comments
Hi Guys,
I've just upgraded from 0.4.5. I know, i should have upgraded much sooner. I haven't touched the orm part of my app in a very long time. It now seems that the argument passing functionality of my selectors is broken, and the selector never receives the argument. All my other selectors that don't take arguments are working properly.
I initially wrote my setup with redux-orm-primer as a reference. It looks like it's very outdated and i'm not sure the way i approach selectors using the reselector module is even supported/the way to do it anymore.
I've tried for hours refactor my code to reflect how one should do it in the current release, but i just run into more serious runtime errors, at this point i feel so rusty that i'm afraid i'll only break it further.
How should i modify my packWithBundleId selector (or other parts of my setup) so that it receives the bundleId as an argument, and is able to return the Pack with the corresponding bundleIdentifier?
Any help is very appreciated. Thanks.
store.js
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducers/index'
import orm from 'xxx/src/redux/orm'
import sagas from 'xxx/src/redux/sagas'
const sagaMiddleware = createSagaMiddleware()
const createStoreWithMiddleware = applyMiddleware(sagaMiddleware)(createStore)
const initialState = orm.getEmptyState()
const session = orm.mutableSession(initialState)
const {
Pack
} = session
export const store = createStoreWithMiddleware(rootReducer, {
content: initialState
})
sagaMiddleware.run(sagas)
orm.js
import {ORM} from 'redux-orm'
import Stack from './models/stack'
import Card from './models/card'
import Pack from './models/pack'
const orm = new ORM({
stateSelector: state => state,
})
orm.register(Card, Stack, Pack)
export default orm
selectors.js
import { createSelector } from 'reselect'
import { createSelector as ormCreateSelector } from 'redux-orm'
import orm from 'xxx/src/redux/orm'
export const ormSelector = state => state.content
export const packWithBundleId = createSelector(
ormSelector,
(state, bundleId) => bundleId,
ormCreateSelector(orm, (session, bundleId) => {
// bundleId is always undefined
return session.Pack.all().filter(pack => pack.bundleIdentifier == bundleId).first()
})
)
calling the selector
const state = store.getState()
const pack = yield Selectors.packWithBundleId(state, previousPurchase.productId)
Hey August,
I've just upgraded from 0.4.5. I know, i should have upgraded much sooner.
now that's an impressive number 😂 As you were so kind to post all the relevant code I'll change the files for you and explain what you need to change and why.
store.js
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'
import {createReducer} from 'redux-orm'
import rootReducer from './reducers/index'
import orm from 'xxx/src/redux/orm'
import sagas from 'xxx/src/redux/sagas'
const sagaMiddleware = createSagaMiddleware()
const createStoreWithMiddleware = applyMiddleware(sagaMiddleware)(createStore)
export const store = createStoreWithMiddleware(rootReducer, {
content: createReducer(orm)
})
sagaMiddleware.run(sagas)
No need to create a sesson here and also no need to grab the initial state. Creating an actual reducer should be all you need to do unless I'm missing something else.
orm.js
import {ORM} from 'redux-orm'
import Stack from './models/stack'
import Card from './models/card'
import Pack from './models/pack'
const orm = new ORM({
stateSelector: state => state.content,
})
orm.register(Card, Stack, Pack)
export default orm
The stateSelector
tells the createSelector
function in which branch of the global Redux state tree the selector will be able to retrieve the ORM's current state. In other words, you need to retrieve the ORM state branch from wherever you store the result of createReducer(orm)
. It replaces the ormSelector
that you had in selectors.js
before.
selectors.js
import { createSelector } from 'redux-orm'
import orm from 'xxx/src/redux/orm'
export const packWithBundleId = createSelector(
orm,
(state, bundleId) => bundleId,
(session, bundleId) => {
// bundleId is always undefined
return session.Pack.all().filter(pack => pack.bundleIdentifier == bundleId).first()
}
)
You should no longer pass ormSelector
to createSelector. Any selector spec beginning with orm
or the orm
itself will suffice. If you pass orm
it will be converted to a session at the respective position in the output selector's parameter list. If you pass ormSelector
you will get the (useless) raw ORM state and not a session.
There's also no need to import createSelector
from reselect here because you actually access the ORM state in your selector: the createSelector
function from Redux-ORM requires you to pass at least one input selector beginning with orm
.
I highly recommend not returning model instances from selectors but their refs instead (which is the original object you pass when creating the instance). So the selector would ideally look like this:
export const packWithBundleId = createSelector(
orm,
(state, bundleId) => bundleId,
(session, bundleId) => {
// bundleId is always undefined
const instance = session.Pack.all().filter(pack => pack.bundleIdentifier == bundleId).first();
if (!instance) return null;
return instance.ref;
}
)
PS: I took the liberty to format your code using JS syntax highlighting.
Thank you so much for the help @haveyaseen, seeing all the changes together with your notes was immensely helpful.
Everything is back up and running, and i'll put the ref change on the next thing to do for all my selectors that don't use it already.
For reasons i do not know, but you might. If i use createReducer, rather than getEmptyState(), the session property inside my selectors changes to a callback.
With this method
export const store = createStoreWithMiddleware(rootReducer, {
content: createReducer(orm)
})
The value of the session argument inside the selectors is this:
(state, action) {
var session = orm.session(state || orm.getEmptyState());
updater(session, action);
return session.state;
}
But with this method, i get the correct session value
const initialState = orm.getEmptyState()
export const store = createStoreWithMiddleware(rootReducer, {
content: initialState
})
Great news! I now see the problem. You want to create the content
key in rootReducer
, not in the second argument to createStore
/createStoreWithMiddleware
.
reducers/index.js
import {combineReducers} from 'redux'
import {createReducer} from 'redux-orm'
import orm from 'xxx/src/redux/orm'
export default combineReducers({
// whatever else
content: createReducer(orm)
})