ReactJS pagination using the will_paginate gem

mmontoya
3 min readOct 11, 2016

1.) Install the gem in your Gemfile:

gem 'will_paginate', '~> 3.1.0'

In your routes.rb file you must have something like:

namespace :api, defaults: {format: :json} do
namespace :v1 do
post '/articles/search/'=>'articles#search'
end
end

save and restart your rails server.

2.) Set up the normal will_paginate query in your Rails controller:

# file: app/controllers/api/v1/articles_controller.rbdef search
myparams = { page: params[:page], :per_page: params[:per_page] }
articles = Articles.where("description ILIKE #{params[:term]}").paginate(myparams)

response = {articles: articles, total: articles.total_entries}
return render json: response
end

NOTE: If you are filtering your params with:

params.require(:article).permit(:title…

add the symbols, :term, :page and :per_page.

3.) Test your request inside the Rails console:

app.get '/'
token = app.session[:_csrf_token]
app.post '/api/v1/articles/search', params: {article:{term: 'lorem ipsum', page: 2, per_page: 9}}, headers: {'X-CSRF-Token' => token}
app.response.body

4.) Rails, is ready, now the front-end part. The action that loads the response:

// file: REACT/actions/articles.jsxrequire('es6-promise').polyfill();
require('isomorphic-fetch');
export const SEARCH_ARTICLES = 'SEARCH_ARTICLES';export function searchArticles(term, page, per_page) {
return function (dispatch) {
let data = {
method: 'POST',
credentials: 'same-origin',
mode: 'same-origin',
body: JSON.stringify({article: {term: term,
page: page,per_page: per_page}}),
headers: {
'Accept': ‘application/json’,
'Content-Type': ‘application/json’,
'X-CSRF-Token': $(‘meta[name=”csrf-token”]’).attr(‘content’)
}
}
return fetch(‘/api/v1/articles/search/’, data)
.then(response => response.json())
.then(json => dispatch(setSearch(json)));
}
};
function setSearch(SearchArrayProp) {
return {
type: SEARCH_ARTICLES,
SearchArrayProp
}
};

5.) Your Redux reducer is simple:

// file: REACT/reducers/my_rdcr.jsx
import { SEARCH_ARTICLES} from '../actions/articles'
const my_rdcr = (state = {}, action) => {
switch (action.type) {
case SEARCH_ARTICLES:
return Object.assign({}, state, {
SearchArrayProp: action.SearchArrayProp.articles,
TotalNumberProp: action.SearchArrayProp.total
});
}
}
export default my_rdcr;

6.) Finally, our React component:

// file: REACT/components/ArticlesSearchComponent.jsx 
'use strict'
import React, { PropTypes, Component } from ‘react’
import { Link, browserHistory, withRouter } from ‘react-router’
import { connect } from ‘react-redux’
import { Button } from ‘react-bootstrap’
import * as ArticlesActionCreators from ‘../actions/articles’
class ArticlesSearchComponent extends Component {
constructor(props) {
super(props)
this.state = {
term: '',
page: 1,
rows_per_page: 10
}
this.loadArticles = this.loadArticles.bind(this);
this.initPageNumbers = this.initPageNumbers.bind(this);
this.getPage = this.getPage.bind(this);
}
loadArticles(){
let action = ArticlesActionCreators.searchArticles( this.state.term, this.state.page, this.state.rows_per_page);
this.props.dispatch(action);
}
handleChange(event){
this.setState({term:event.target.value});
}
getPage(page) {
this.setState({page: page});
let self = this;
setTimeout(function(){ self.loadArticles() ; }, 1000);
}
initPageNumbers(){
let total_rows = parseInt(this.props.TotalNumberProp);
let page = 1;
let rows = [];
for(var x = 0; x < total_rows; x += this.state.rows_per_page){
rows.push(page);
page++;
}
return rows;
}
render() {
let rows = this.initPageNumbers();
return (
<div id=”responsive”>
<div>
<form>
<label htmlFor=”term”>Search for articles:</label>
<input className="form-control" name=”term” value={this.state.terms} onChange={this.handleChange.bind(this)} />
<Button onClick={this.loadArticles.bind(this)}>Search</Button>
</form>
</div>
{ this.props.SearchArrayProp.length ? null : <div>No matches</div> }
<div className="container_div">
{this.props.SearchArrayProp.map((article, i) =>
<div key={i}> <b>{i+1}.-</b>: {article.title} {article.id}
</div>
)}
</div>

<div>
<ul className="pagination">
{rows.map((r) =>
<li key={r}>
<a href={"#"+r} onClick={() => this.getPage(r)}>{r}</a>
</li>
) }
</ul>
</div>
</div>
);
}
}
ArticlesSearchComponent.propTypes = {
SearchArrayProp: PropTypes.array,
TotalNumberProp: PropTypes.number
}
ArticlesSearchComponent.defaultProps = {
SearchArrayProp: [],
TotalNumberProp: 0
}
const mapStateToProps = (state) => {
return {
SearchArrayProp: state.my_rdcr.SearchArrayProp,
TotalNumberProp: state.my_rdcr.TotalNumberProp
}
}
// binding React-Router-Redux
export default withRouter(connect(mapStateToProps)(ArticlesSearchComponent));

And there you have! Pagination with React and Ruby on Rails. Note that I’m using the CSS “pagination” class from Bootstrap.

You can test your request with this:

# file: spec/requests/articles_spec.rb
RSpec.describe '/api/v1/articles', type: :request do
describe 'POST /search/' do
it '.search for articles' do

art1 = FactoryGirl.create :article, description: "Product One"
art2 = FactoryGirl.create :article, description: "Product Two"
art3 = FactoryGirl.create :article, description: "Lorem Ipsum"
post ‘/api/v1/articles/search’, params:
{article: {term: 'product', page: 1, per_page: 10 }},
headers: { ‘Accept’ => ‘application/json’ }, as: :json
json = JSON.parse(response.body)
expect(response.status).to eq 200
expect(json.length).to eq 2
expect(json.second['description']).to match("Product Two")
end
end
end

--

--