Skip to content

Instantly share code, notes, and snippets.

@ynonp
Last active July 28, 2017 07:10
Show Gist options
  • Save ynonp/0c266d4c7b77ed9da0d3 to your computer and use it in GitHub Desktop.
Save ynonp/0c266d4c7b77ed9da0d3 to your computer and use it in GitHub Desktop.
react-rails and redux

react-rails and Redux

The main issue I found when integrating react-rails and flux/redux is managing application state. In redux I'd like to have a single application state serialized from the server and parsed in the client, and to use that state to initialize the main store. Something like:

initialState = JSON.parse(window.__APPSTATE__);

But that won't work since react-rails splits its work to 2 phases: it first reads all .js files in the renderer initialize phase and every call to the view helper only calls render.

Solution is described below by using a new renderer that:

  1. takes an @appstate attribute in its prerender options
  2. uses the before_render hook to inject JS code before calling render. That code dispatches a new action setting the application state.
  3. only then it calls the component's render function

I'm not sure it's the best solution or how to generalize it, so any feedback is welcomed.

module React
module ServerRendering
class AppstateRenderer < SprocketsRenderer
attr_accessor :appstate
def render(component_name, props, prerender_options)
# pass prerender: :static to use renderToStaticMarkup
@appstate = {}
if prerender_options.respond_to? :fetch
@appstate = prerender_options.fetch(:appstate, {})
end
super(component_name, props, prerender_options)
end
def before_render(component_name, props, prerender_options)
super(component_name, props, prerender_options)
dispatch = "window.mainStore.dispatch"
action = "window.actions.replace_state"
new_state = "'#{@appstate.to_json}'"
old_state = "window.__APPSTATE__"
"if ( #{new_state} !== #{old_state} ) #{dispatch}(#{action}(JSON.parse(#{new_state})))"
end
end
end
end
# config/initializers/react.rb
TichnutTV::Application.configure do
config.react.server_renderer = React::ServerRendering::AppstateRenderer
end
window.actions = {
replace_state: function(state) {
return { type: REPLACE_STATE, payload: state };
}
};
function mainStore(state, action) {
if ( typeof state === 'undefined' ) {
state = initialState;
}
switch( action.type ) {
case REPLACE_STATE:
return action.payload;
default:
return state;
}
}
<%= react_component('window.components.Main',
{},
{ prerender: { :appstate => @appstate }}) %>
@justin808
Copy link

Please use the https://github.com/shakacode/react_on_rails/ gem. We've already got an example of this here: https://github.com/shakacode/react-webpack-rails-tutorial/. Please see the recently accepted PRs. We're very actively developing this technology. My new company specializes in react + redux + react-router + rails. Please post your questions to http://forum.railsonmaui.com. We've got a slack room as well for discussing this.

@jhilden
Copy link

jhilden commented Dec 11, 2015

thanks a lot for the gist @ynonp I used it as a base for https://gist.github.com/jhilden/44378421ad54e617b900

one question. shouldnt the output of super inside the before_render method also be returned together with the new output?

like:
super + "if ( ..."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment