redux-orm / redux-orm

NOT MAINTAINED – A small, simple and immutable ORM to manage relational data in your Redux store.

Home Page:https://redux-orm.github.io/redux-orm/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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)
})