30

I'm currently not happy with the hacks I've come up with using Flux, and now Redux for populating initialState in my store.

I always send a payload of JSON down with the initial page load. Universal JavaScript is not an option in some of my applications as they are not all backed by Node. I would like to focus on "best practices" and patterns around how the data is preloaded on the client. Then, I feel that moving to the server is trivial.

Here’s a gist of what I’m currently doing at work and in a few other toy apps using Redux:

https://gist.github.com/kevinold/5767bb334472b7e2bfe3

To summarize:

  • I’m sending global variables down with the page
  • In componentDidMount() I'm dispatching actions that “receive” and handle the various pieces of data. (These “receive” methods are used when processing these bits of data when they are fetched async via a request action, and I’m repurposing them here since I already have the data.)

This works and there aren’t any issues that we’re running into, but it feels like a hack I’m not really happy with. Also, I feel that the initial state of Redux isn’t happy with me doing it this way.

According to http://rackt.org/redux/docs/recipes/ServerRendering.html and the section “The Client Side”, I am able to pass initialState to my store.

Here’s a patch I have for another project I'm helping with (KeystoneJS). I'm passing it's Keystone object into the initialState:

https://github.com/kevinold/keystone/commit/6f80c2f6f1e5c081361369a8bb31b75f1e62460f#diff-cd8e9933209e834b0519a0257bcfa914R8

While that does work, as you can see, I’m forced to match the shape of the output of my overall combinedReducers (https://github.com/kevinold/keystone/commit/6f80c2f6f1e5c081361369a8bb31b75f1e62460f#diff-b4b498ca92c4d05e050b45c725c26f9d) or I will get console warnings, etc.

I might be extremely lazy in dealing with this, but trying to limit having to update another piece of data when I add/change anything related to reducers (add a reducer, how they are composed, etc.).

I realizing I might have to in order to get this initialState into the reducers properly.

I'm currently digesting how (https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/client.js#L25) handles it's initialState.

I’d really appreciate some feedback on "best" and "real world" practices that are working for others.

Kevin Old
  • 969
  • 1
  • 9
  • 20

1 Answers1

21

I don’t fully understand the question but I’ll try my best to answer.

In componentDidMount() I'm dispatching actions that “receive” and handle the various pieces of data. (These “receive” methods are used when processing these bits of data when they are fetched async via a request action, and I’m repurposing them here since I already have the data.)

This is a bad place to dispatch them. If you choose to dispatch actions instead of prodiving initial state directly, do that before rendering. In other words, do that right after creating the store! This way you don’t render the initial blank state.

While that does work, as you can see, I’m forced to match the shape of the output of my overall combinedReducers (https://github.com/kevinold/keystone/commit/6f80c2f6f1e5c081361369a8bb31b75f1e62460f#diff-b4b498ca92c4d05e050b45c725c26f9d) or I will get console warnings, etc.

You’re not supposed to create the initialState object manually. You’re supposed to create a Redux store on the server, dispatch actions there to prefill it with data, and when it’s ready, call store.getState() to retrieve the state you want to pass down to the client. On the client, you’d read it from a global variable, and create the client store instance with it. In other words you never have to manually create initialState—you’re supposed to grab it on the server with store.getState(), pass it to client, and create a store with it.

Therefore I don’t understand the problem you describe. If your reducer names or nesting changes, it will change on the server too. There is nothing you need to do to “fix it”—if you use Redux both on client and server, their state structure will match.

I’d really appreciate some feedback on "best" and "real world" practices that are working for others.

  1. If you use Redux on server, prefill the store as described above, and pass its state down to the client. In this case you need to use the initialState argument of createStore().

  2. If you for some reason don’t use Redux on server but you want to prefill the data anyway (e.g. maybe you use something other than Node), the approach with dispatching actions is your next best bet because it doesn’t require your backend to know the state shape. In this case, you should pass those actions as JSON to the client, and dispatch() all of them right after the store is created.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • Thanks Dan, this does make sense and help. – Kevin Old Nov 25 '15 at 19:33
  • 1
    I am using Rails and Phoenix backends and not using Redux on the server. As you can see in https://gist.github.com/kevinold/5767bb334472b7e2bfe3, I have multiple JSON objects coming from the server. Combining these into the initialState on the server would certainly help. Many thanks for your feedback and clarifications on the subject! – Kevin Old Nov 25 '15 at 19:39
  • @KevinOld If you don't use Redux on server I'd suggest keeping your current approach of dispatching separate actions, so that you don't need to hardcode state shape into your server. Just make sure you dispatch before rendering! – Dan Abramov Nov 25 '15 at 20:57
  • Thanks! I appreciate the insight and would like to move those last two points into a PR for the docs, if ok with you. It's also nice to see your perspective in coming from a Node server with Redux. Outside of a Node environment there can be many reason why using Redux on the server isn't possible, despite that technically it can be done. In other apps where I have a bit more control, I'll work to keep server state completely in sync with client. Thanks again – Kevin Old Nov 25 '15 at 21:09
  • @KevinOld PR would be awesome. – Dan Abramov Nov 26 '15 at 01:05
  • @DanAbramov But in the Redux-examples, you are dispatching fetch actions in componentDidMount[0]? Edit: If you know the first view should be pre-populated, we should fetch that right after connecting to the store, and still have fetch if needed in componentDidMount? [0]: http://rackt.org/redux/docs/advanced/ExampleRedditAPI.html#-containers-asyncapp-js – user2602152 Feb 09 '16 at 13:23
  • @user2602152 I think normally you want the `initialState` population to be an optimization. The app should be able to boot up without it, in my opinion. And route arguments change so you'll want some code to fetch stuff on change anyway. – Dan Abramov Feb 09 '16 at 20:26
  • @DanAbramov is there a proper methodology to reading from local storage and rehydrating the store when one uses multiple reducers? – james emanon Feb 19 '16 at 03:21
  • 1
    @jamesemanon Read from the storage and pass that as initialState. Subscribe to the store and write the current state to the storage once in a while. – Dan Abramov Feb 19 '16 at 12:53
  • 1
    @DanAbramov Do you have an example of option #2 in practice? I'm working on a front end Redux application with a .NET backend that wont be able to use Redux. – Rorschach120 Jan 04 '17 at 21:50