Using the will_paginate gem to generate a React renderable HTML string

Generally when I post about programming it’s because I’ve finally found a solution to something that has been driving me crazy, some problem with a lot of documentation to read through and not a lot of Stack Overflow answers (and if they do exists, the use case is always a little off). This is no exception!

First, why would you want to combine React and Ruby gems? In my case, I thought it was the simple, lazy path. Way back when, the site I work for was built using will_paginate for all pagination. I recently ran into a case where I needed to include that pagination in a React app. It wasn’t for anything fancy, the links and logic needed to match the rest of site. So, trying to re-create the gem magic in React seemed like overkill compared to creating a partial with the code, generating an HTML string using the Rails render_to_string method, and sending it to React to dangerouslySetInnerHTML.

Well, it turns out will_paginate uses the url_for method and it was not happy to be called without the request data (undefined method `host’ for nil:NilClass?!) it uses to build urls. After a lot of hunting around, I came across this pull request to get will_paginate working with mountable engines that seemed adaptable. My helper accepts the request from the controller and pulls out the necessary fields to send with it’s own instance of url_for

module WillPaginateHelper
  class StringLinkRenderer < WillPaginate::ActionView::LinkRenderer
    protected

    def url(page)
      @base_url_params ||= begin
        url_params = merge_get_params(default_url_params)
        url_params[:only_path] = true
        merge_optional_params(url_params)
      end

      url_params = @base_url_params.dup
      add_current_page_param(url_params, page)

      if @options[:url_builder]
        @options[:url_builder].call(url_params)
      else
        @template.url_for(url_params)
      end
    end
  end

  def will_paginate_string(collection = nil, options = {}, request = nil)
    params = {
      :host => request[:host],
      :controller => request[:controller],
      :action => request[:action]
    }

    unless request.query_parameters.empty?
      request.query_parameters.each do |k, v|
        params[k] = v
      end
    end

    options.merge!(
      :renderer => WillPaginateHelper::StringLinkRenderer,
      :url_builder => ->(params) { Rails.application.routes.url_for(params) },
      :params => params
    )

    will_paginate(collection, options)
  end
end

(GitHub Gist)

Then, in the partial that is being rendered to a string, it’s possible to call will_paginate_string(collection, options, request) and the results can be sent as a JSON response to render in React.

Admittedly, Rails isn’t my strong suit so I’d be interested to hear if I missed some easier way to do this.

Comments