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.

Code For This Video

Get the code for this video.

Episodes