React - Limiting
In this video we are going to add in an ability to Limit the total number of results displayed per page.
As with all the other methods - Sorting, Filtering, and Paginating - we do this via modifying the URL we use when calling the API.
As we saw in the Angular implementation, after modifying the URL, it may look something like:
http://api.symfony-3.dev/app_dev.php/posts?limit=10
This is functionality provided by the KNP Pagination Bundle which is what is powering Pagination on our API.
Some other variations of the URL that would also work:
http://api.symfony-3.dev/app_dev.php/posts?limit=7500
http://api.symfony-3.dev/app_dev.php/posts?limit=50
http://api.symfony-3.dev/app_dev.php/posts?limit=
http://api.symfony-3.dev/app_dev.php/posts
Again, read the Angular Limiting write up for more info on this if unsure.
Quick Recap
In preparation for this (and filtering), we have already updated the function which builds the URL and sends the request (oops, did I just break the Single Responsibility Principle!) but to quickly show the code here again:
// /src/actions/blogPostActions.js
import fetch from 'isomorphic-fetch';
export function fetchBlogPosts(page, limit, filter, sort, direction) {
let p = new URLSearchParams();
p.append('page', page || 1);
p.append('limit', limit || 5);
p.append('filter', filter || '');
p.append('sort', sort || '');
p.append('direction', direction || '');
return fetch('http://api.symfony-3.dev/app_dev.php/posts?' + p, {
method: 'GET',
mode: 'CORS'
})
.then(res => res.json())
.catch(err => err);
}
We use the URLSearchParams
object to help us create a URL with nicely formatted parameters. Then we use fetch
to GET
that URL, and we simply return the resulting JSON (res.json()
).
To each one of these parameters we pass in either the value from the function signature:
export function fetchBlogPosts(page, limit, filter, sort, direction) {
Or we provide a sane default.
In the case of the limit
, we use a default of 5. If everything works, we would expect our initial page load to have 5 results per page, unless things have gone awry.
If any of this is new to you, or you have questions, please watch the previous videos in this series where this is all explained further.
Adding An input
For Limit
We need to take some input from the user. Nothing unexpected here, we just add a input
element to our render
block.
// /src/components/Table.js
limitHandler(e) {
console.log('e is a SyntethicElement object', e);
console.log('to get the user's input value, we need to', e.target.value);
}
render() {
return (
<div>
<label htmlFor="limiter">Limit</label>
<input type="text"
id="table_limiter"
value={this.state.limit}
onChange={this.limitHandler.bind(this)}
className="form-control"/>
<table className="table table-hover table-responsive">
<thead>
<tr>
* snip *
Notice the use of htmlFor
in the label
, instead of the usual for
we'd use in normal HTML.
Also, the use of className
instead of class
.
These will inevitably catch you out at some point in the future :)
We have also added in an onChange
event listener.
Simply put, whenever a user enters some info into this text box, we are going to call this function with the current value.
In our case, we are going to call the limitHandler
function.
This function is going to be called with an instance of React SyntheticElement
]3, from which we need the e.target.value
, which will contain the value that the user input into the text box.
If we write the function using bind
like this then the confusing part is where the e
value comes from. It is passed in implicitly when the onChange
function is invoked. Personally, I find this more confusing, and have come to prefer the explicitness of an ES6 fat-arrow function.
Here is a good explanation of using JavaScript's function bind
.
As covered previously, we can either do a .bind(this)
to lock the value of this
to the Table
component, or we could re-write this using an ES6 arrow function:
// /src/components/Table.js
limitHandler(limit) {
console.log('limit contains the value of the text box', limit);
}
render() {
return (
<div>
<label htmlFor="limiter">Limit</label>
<input type="text"
id="table_limiter"
value={this.state.limit}
onChange={(e) => this.limitHandler(e.target.value))}
className="form-control"/>
<table className="table table-hover table-responsive">
<thead>
<tr>
* snip *
Now we have the user's input value - whichever way you choose to implement - we can use this value as part of the query string when calling our API.
input
, Meet API
Much like in our Sorting video we aren't defining a definitive limit
function inside our Table
component. Instead, we will pass a function through - via props
.
We can easily do this, just like in the sorting example, by defining a new prop
, in our case - onLimit
:
// /src/containers/list.js
render() {
let totalPages = Math.ceil(this.state.totalItems / this.state.numItemsPerPage);
return (
<div>
<Table blogPosts={this.state.blogPosts}
onDelete={this.onDelete.bind(this)}
onPaginate={this.onPaginate.bind(this)}
onLimit={this.onLimit.bind(this)}
></Table>
</div>
);
}
We haven't defined that function either just yet, but we now have a prop
called onLimit
, which will contain our function to call.
Again, remember to use propTypes
to ensure that a function is being passed in. This will make everyones life easier, including your own.
Anyway, we can now update our limitHandler
function to call this passed in function with the value we have just received from the end user:
// /src/components/Table.js
// if using es6 fat arrows
limitHandler(limit) {
this.setState({ limit });
this.getBlogPosts(this.state.currentPageNumber, limit);
}
// if using this.limitHandler.bind(this)
limitHandler(e) {
this.setState({ limit: e.target.value });
this.getBlogPosts(this.state.currentPageNumber, limit);
}
And with that, we have ourselves a very basic limit
.
There are problems with this approach. It's not a production ready setup. It is intended to illustrate connectivity from inputs to your API, rather than be an off-the-shelf implementation for your production project.