Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging part of state from server into initial state #433

Closed
roganov opened this issue Aug 9, 2015 · 16 comments
Closed

Merging part of state from server into initial state #433

roganov opened this issue Aug 9, 2015 · 16 comments

Comments

@roganov
Copy link

roganov commented Aug 9, 2015

Let's consider the following snippet:

const initialState = {
    options1: [],
    options2: [],
    input1: "Your text",
    input2: "Your text",
}

function store(state = initialState, action) {
   ...
}

I want to hydrate this store with options1 and options2 with values from the server, but at the same time I want input1 and input2 to be merged from the initialState object.
It would've been easy to subscribe to the '@@INIT' action and do the initialization I need, but since it's been explicitly said (#186) that this is an anti-pattern I've ruled this option out.
Two other options I've come up with:

  1. Check whether input1 and input2 are defined:
function store(state = initialState, action) {
   if(state.input1 == null && state.input2 == null) {
       state = {...initialState, ...state}
  }
  ...
}

But this is very hacky and someone else is going to ask why this check is there.
2) Modify initial state that comes from the server before passing it as a second argument to createRedux.
Probably better, but I'd like to keep initial state in the reducer's module.

What is the preferred way to achieve what I want? I keep thinking that there should be something like '@@hydrate' action where we can do initialization.

@merk
Copy link
Contributor

merk commented Aug 9, 2015

We're firing an action as soon as the store is created for hydration which reads from sessionStorage, localStorage or other services to retrieve information.

@roganov
Copy link
Author

roganov commented Aug 9, 2015

@merk Do I understand correctly that you do something like this:

const redux = createRedux(stores)
const hydro = collectHydro();
redux.dispatch({type: 'HYDRATE', hydro});

Or do you still pass hydrated state a second argument to createRedux?

@merk
Copy link
Contributor

merk commented Aug 9, 2015

My understanding of the second argument to createStore is for state serialized from the server. The kind of information we hydrate is authentication tokens and things that should persist across reloads, nothing more.

But yes, thats basically what we're doing.

@gaearon
Copy link
Contributor

gaearon commented Aug 9, 2015

Indeed, the state from server should be passed as second argument to createStore (createRedux in 0.12 API but you should update).

It will be merged with the initial state of your reducers ("stores" in 0.12) specified as default parameters.

@gaearon gaearon closed this as completed Aug 9, 2015
@roganov
Copy link
Author

roganov commented Aug 9, 2015

@gaearon
Sorry, but state from server seems to replace state, not merge with initial state of my reducer.
E.g.:

function myReducer(state = {bar: "bar"}, action) {
   ...
}
store = composeStores({myReducer});
createRedux(store, {myReducer: {foo: "foo"}})

On next action dispatch state of myReducer won't have bar property.

@gaearon
Copy link
Contributor

gaearon commented Aug 9, 2015

Do you want to combine parts of state from server with initial state? I assumed you wanted to prefill some reducers, but not the others.

In this case don't use ES6 default parameters and do this manually, or via a custom "hydrate" action. Both options are fine.

@gaearon
Copy link
Contributor

gaearon commented Aug 9, 2015

const initialState = {
    options1: [],
    options2: [],
    input1: "Your text",
    input2: "Your text",
}

// We don't call them stores anymore! Check out 1.0 API and terminology.
// http://gaearon.github.io/redux

function reducer(state, action) {

  // I want to hydrate this reducer's state with options1 and options2 with values from the server
  // but at the same time I want input1 and input2 to be merged from the initialState object. 

  if (typeof state === 'undefined') {
    // Server didn't supply any state. Use local state.
    state = { ...initialState, hydrated: true };
  } else if (!state.hydrated) {
    // Server supplied state, but it's not merged with our local initial state yet.
    state = { ...initialState, ...state, hydrated: true };
  }

  // do something

  return state;
}

Another way of writing the same code:

const initialState = {
    options1: [],
    options2: [],
    input1: "Your text",
    input2: "Your text",
}

function reducer(state = initialState, action) {
  if (!state.hydrated) {
    state = { ...initialState, ...state, hydrated: true };
  }

  // do something

  return state;
}

@roganov
Copy link
Author

roganov commented Aug 9, 2015

@gaearon Thanks, adding an extra flag seems to be the best way! Really appreciate your help.

@kristoferjoseph
Copy link

I feel like merging state should be the default.
Meaning reducers should be able to pre-fill state with default and then use the server state to override default state.
Definitely surprised me that this was not the default behavior

@gaearon
Copy link
Contributor

gaearon commented May 18, 2016

Meaning reducers should be able to pre-fill state with default and then use the server state to override default state.

This is exactly how it works when you use combineReducers().

@kristoferjoseph
Copy link

@gaearon Are you saying to put the server state in combineReducers instead of in createStore like the examples show?

@gaearon
Copy link
Contributor

gaearon commented May 18, 2016

Are you saying to put the server state in combineReducers instead of in createStore like the examples show?

The code is in this thread is from the times when Redux 1.0 wasn’t even a thing 😄 .

This works fine today:

const a = (state = 5, action) => state
const b = (state = 10, action) => state
const reducer = combineReducers({ a, b })
const store = createStore(reducer, { a: 42 })
console.log(store.getState()) // { a: 42, b: 10 }

@gaearon
Copy link
Contributor

gaearon commented May 18, 2016

Also, I wrote above that merging “just works” when you use combineReducers(): #433 (comment).

@roganov wanted something more sophisticated:

Sorry, but state from server seems to replace state, not merge with initial state of my reducer.

By default combineReducers() will merge only top-level object. If you want deeper merging, you need to implement it by hand because Redux can’t guess what kind of deeper merging you want. Still, you can use combineReducers() multiple times to make it automatic.

@kristoferjoseph
Copy link

You sure about that?

@gaearon
Copy link
Contributor

gaearon commented May 18, 2016

Yes, I am sure about that. 😉

@nathanredblur

This comment has been minimized.

@reduxjs reduxjs locked as resolved and limited conversation to collaborators Jun 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants