gajus / redux-immutable

redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state.
Other
1.88k stars 82 forks source link

Why can't I use Immutable.Record as initialState in rootReducer? #66

Open JustFly1984 opened 7 years ago

JustFly1984 commented 7 years ago

As I can see in https://medium.com/@fastphrase/practical-guide-to-using-immutablejs-with-redux-and-react-c5fd9c99c6b2 It should be possible to use Record as initialValue, but using combineReducers I get an Error, if I use Immutable.Record instead of Immutable.Map in redux createStore . Please explain.

I use redux-immutable's combineReducers and all my reducers now made with Immutable.Record. But as it is in example in redux-immutable github examples, store should be created with Immutable.Map as initialState.

const initialState = Immutable.Map()
const store = createStore(rootReducer, initialState)

I tried to change it to

const initialState = Immutable.Record()
const store = createStore(rootReducer, initialState)

But I'm getting errors from Immutable lib. Is there some workaround? It could be very helpful, if someone point me in the right direction.

PS. "react-redux": "5.0.5"

The main reason for the issue is API.

Using Record as initialState allows to get data from state in connected components I can:

export default connect(
  state => ({
    someProp: state.reducer.value
  })
)(SomeComponent)

With Map i can only get data:

export default connect(
  state => ({
    someProp: state.getIn(['reducer', 'value'])
  })
)(SomeComponent)
JM-Mendez commented 7 years ago

Immutable records cannot be instantiated generically like map can. Have you tried to create a record, and then instantiate the initial state like so? I set the default values to undefined, but they can be anything you'd like.

const expectedState = Record({
    bar: undefined,
    foo: undefined
})

const initialState = expectedState({
    bar: barReducer,
    foo: fooReducer
})

// if the defaults are the same
const expectedState = Record({
    bar: barReducer,
    foo: fooReducer
})

const initialState = expectedState()
JustFly1984 commented 7 years ago

Got it. So Records in Redux state is wrong way.

2017-08-16 6:28 GMT+08:00 John Mendez notifications@github.com:

Had to come back here cuz I just realized something. I wouldn't recommend using an immutable record for the state tree. Records don't allow the set method after creation, so you'd have to recreate the entire tree for every dispatch action.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/gajus/redux-immutable/issues/66#issuecomment-322607029, or mute the thread https://github.com/notifications/unsubscribe-auth/ACJseSvgFJ8_knsxg5q-UqFTeR_3OetMks5sYhuDgaJpZM4OK7rh .

sylvain-pauly commented 6 years ago

@JustFly1984 Can you please, explain what does he mean by

Records don't allow the set method after creation

https://facebook.github.io/immutable-js/docs/#/Record/set

Am I missing something ? I started to make records for my reducers's states, mostly to be able to type them with flow (since the Maps shape can't be typed).

Please, tell me it's ok with Immutable v4... I'm starting to think that Immutable is not a library you want in your redux app - bad news for me, I have a huge app running on Immutable Redux state.

JM-Mendez commented 6 years ago

You can use records in your redux app. The immutable api set method on Record collections works just fine. But the OP wanted to use records in order to use dot notation, and that only works for accessing property values, not setting them. Which means he'd still be forced to use the immutable api.

myRecord.b // 3
myRecord.b = 5 // throws Error

Also, you have to explicitly create each record instance with every single property value when initializing your state.

recordState: Record({ a: 1, b: 2 })

This works fine if you know for a fact that your initial state will always be initialized to a:1, b:2, and will never add another property. So this won't work:

recordState.set('c', '3') // throws no error since immutable ignores setting non-existent record properties

Thus you'd be forced to mix a top level record state with maps, and then will end up accessing them using the immutable api anyways.

Lastly, v4.0.0-rc.1 introduced breaking changes to records. They are no longer an immutable collection type, so sequence api methods like map, filter, forEach are no longer available. https://github.com/facebook/immutable-js/releases/tag/v4.0.0-rc.1


Maps can be typed just like records. All that having records buys you is faster access and a guarantee of having specific properties always initialized. Personally, I use a map as the top level state, and then use records for my collections and all works perfectly. And I'm able to type them using typescript.

i11v commented 6 years ago

@JM-Mendez, hi! Sorry for unrelated question: can you please provide an example of typing Map on top level state? Thank you :)

JM-Mendez commented 6 years ago

@xomyaq sure. You can type your maps at any level like this.

type firstReducer = () => {}
type secondReducer = () => {}

interface AllReducers {
  root: firstReducer,
  ui: secondReducer,
}

const initialState: Map<keyof AllReducers, AllReducers[keyof AllReducers]> = Map()