Several posts describe displaying flash messages after AJAX calls (for instance, Rails doesn't display flash messages after ajax call and How do you handle Rail's flash with Ajax requests?).
I'm using ReactJS with react-rails and want to do this, and want to take advantage of React's dynamic display rendering.
So I'm answering my own question of how to render flash messages after AJAX calls when using ReactJS below as a way to answer others who might encounter this and get any advice for improving it.
This implementation is based largely on the other SO posts on this subject (see references in question). The code assumes Rails 4.x with the asset pipeline, HAML for the views, Bootstrap for styling, and use of the react-rails gem.
(update: a slightly longer discussion can be found at my blog).
Layout
Here's the app/views/layouts/application.html.haml
file:
!!!
%html
%head
%title
Demo
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= csrf_meta_tags
%body
%div.container
%div#flash_messages
= yield
Note that the div
has an id of #flash_messages
. The placement of this container div determines where the flash message appear.
Controller
The private routine below simply converts the native array-of-arrays uses to be parseable JSON in the header.
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
after_filter :flash_to_http_header
private
def flash_to_http_header
return unless request.xhr?
return if flash.empty?
response.headers['X-FlashMessages'] = flash.to_hash.to_json
flash.discard # don't want the flash to appear when you reload page
end
end
Javascript (ReactJS)
The following javascript is made up of a React class, a global function to process flash messages out of the response header, and a JQuery handler for completion of AJAX calls. It's put into a single JX file (I used app/assets/javascripts/react/flash_messages.js.jsx
) to keep the relevant JS functionality together. I discuss this more below.
/** @jsx React.DOM */
var FlashMessages = React.createClass({
getInitialState: function() {
return {messages: this.props.messages};
},
messages: function (messageArray) {
this.replaceState({messages: messageArray});
},
render: function() {
return (
<div className='flash_messages_component'>
{this.state.messages.map(function(message, index) {
_level = message[0];
_text = message[1];
return (
<div key={index} className={this._flash_class(_level)}>
{_text}
</div>
);
}.bind(this))}
</div>
)
},
_flash_class: function(level) {
var _result = 'alert alert-error';
if (level === 'notice') {
_result = 'alert alert-info';
} else if (level === 'success') {
_result = 'alert alert-success';
} else if (level === 'error') {
_result = 'alert alert-error';
} else if (level === 'alert') {
_result = 'alert alert-error';
}
return _result;
}
});
function handleFlashMessagesHeader(node, xhr) {
var _message_array = new Array();
var _raw_messages = xhr.getResponseHeader("X-FlashMessages")
if (_raw_messages) {
var _json_messages = JSON.parse(_raw_messages);
count = 0
for (var key in _json_messages) {
_message_array[count] = new Array();
_message_array[count][0] = key;
_message_array[count][1] = _json_messages[key];
count += 1;
}
}
node.messages(_message_array);
}
$(document).ready(function() {
var dummy = new Array();
var flashDiv = React.render(<FlashMessages messages={dummy} />, $('#flash_messages')[0]);
$(document).ajaxComplete(function(event, xhr, settings) {
handleFlashMessagesHeader(flashDiv, xhr);
});
});
The FlashMessages
React class does a few things. First, it moves the props into state. Generally this would be an anti-pattern, but doing so enables non-React code to trigger changes when needed. The messages
function is that trigger and is meant to be called by external JS code. The main processing for render
assumes the Rails flash-native array-of-2-element-arrays data structure to keep processing to a minimum and allow the component to be used directly from a view instead of just AJAX calls. Finally, the local _flash_class
method supports Bootstrap styling (which of course could be adjusted for other styling as desired).
The handleFlashMessagesHeader
is a global function for converting the JSON back to an array-of-arrays done by the Rails controller filter method. Note that it takes the DOM element with the id marker from the Rails app view/layout.
The last section is meant to be run on page load and so depends on JQuery ready. The React.render
(formally React.renderComponent
) is saved to a global variable to allow direct calling into the FlashMessages
object's message
method (the dummy
array used is just to seed an empty flashes array when first invoked -- this could probably be handled within the React class too by a null test). Whenever ajaxComplete
is triggered, the handleFlashMessageHeader
function is invoked and passed the React object for updating.
Non-AJAX
Because the React class assumes native array-of-arrays, the view could have simply called a
= react-component 'FlashMessages', flash, :div
instead to display it, but then of course it's of less use since dynamic response isn't supported. However, the ability to invoke it either way gives more flexibility for some use cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With