bita
bita

Reputation: 373

How to filter list then paginate the filtered list?

I have got series of data that contains some objects in one array(json file) and it will be shown by react.

class App extends React.Component {
constructor(props){
super(props);
this.state = {
data: [],
   .
   .
   .
currentPage: 1,
itemsPerPage: 20,
value: '',
filterTerm: null,
startIndex : 0,
endIndex : 4,
 }}}

[{'id': '5c0b6cd9e1382352759fbc25', 'hotelinfo': {'hotelsearch': {'realname': 'Korston Hotel Moscow'}},{'id': '5c0b6cd9e1382352759fbc24', 'hotelinfo': {'hotelsearch': {'realname': 'Lavanta Hotel'}},{'id': '5c0b6cd9e1382352759fbc28', 'hotelinfo': {'hotelsearch': {'realname': 'Stanpoli Hotel'}}]

There is a paging which displays 4 pages by default and show the rest of pages by clicking next button.

render() {
 const { data, currentPage, itemsPerPage, startIndex, endIndex } = this.state;
 const indexOfLastItem = currentPage * itemsPerPage;
 const indexOfFirstItem = indexOfLastItem - itemsPerPage;
 const currentItemsOne = data.sort((a, b) => a.total - b.total);
 const currentItemsSecond = currentItemsOne.filter(this.filterData);
 const currentItems = currentItemsSecond.slice(indexOfFirstItem, indexOfLastItem);
 const renderHotel = currentItems.map((item, i) => {
    return <div class="item">
        <span>{item.hotelinfo.hotelsearch.realname}</span>
    </div>
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(data.length / itemsPerPage); i++) {
    pageNumbers.push(i);
}

const renderPageNumbers = pageNumbers.slice(startIndex, endIndex).map(number => {
    return (
        <li className={(this.state.currentPage === number ? 'active ' : '') + 'controls'}
            key={number}
            id={number}>
            {number}
        </li>
    )
});
return (
    <div>
        <input type="text" value={this.state.value}
            onChange={this.handleInputChange} class="hotelName" />
        <span onClick=this.handleSearch} class="searchbtn">search</span>
        { renderHotel }
        <ul id="page-numbers" class="pagenumDef">
            <li onClick={this.decremant} class="nexprev">
                <span class="fa-backward"></span></li>
            {renderPageNumbers}
            <li onClick={this.increment} class="nexprev"><span class="fa-forward"></span></li>
        </ul>
    </div >
)};

I have an input(class="hotelName") which user start to type in (e.g user type 'Korston' ) and click a button and new result should just contain the data of hotels those contain 'Korston' name.

handleInputChange(event) {
this.setState({ value: event.target.value });
}
handleSearch = () => {
let inputval = this.state.value
const { value } = this.state;
this.setState({ filterTerm: value });
}
filterData = (item) => {
const { filterTerm: term } = this.state;
if (term === null) {
    return true;
}
let inputval = this.state.value
inputval = term.toLowerCase()
    .split(' ')
    .map((s) => s.charAt(0).toUpperCase() + s.substring(1))
    .join(' ');

let realname = item.hotelinfo.hotelsearch.realname
let len = realname.length
if (len !== 0) {
    if (realname.includes(inputval)) {
        return true
    } else {
        return false
    }
}
return false;
}

How can I filter whole the list when I am in any page? For example page 1 contains the information of 'Korston Hotel Moscow'. In page 2 the information of 'Lavanta Hotelof'. By default I am in page 1 and I type either 'Korston' or 'Lavanta' , the result shows me the info of either 'Korston Hotel Moscow' or 'Lavanta Hotelof'. But if I change the page, for example I am in page 2 and type either 'Korston' or 'Lavanta' there would not be any result. How to filter list then paginate the filtered list?

Edit

class App extends React.Component {
constructor(props){
super();
this.state = {
Library:[],
library: null,
perPage: 1,
currentPage: 1,
maxPage: null,
filter: "",
};

$.ajax({ 
url:"/json.bc", 
type:"post",
 data:{
  cityid:"1182348",
  rooms:JSON.stringify({"rooms":[{"adultcount":"1","childcountandage":"0"}]}),
    },
 success:(result)=>{ 
 this.setState({Library: eval(result)}); } 
 })
}

 componentDidMount() {
  this.reorganiseLibrary();
 }

 // Calculates the library
   reorganiseLibrary = () => {
   const { filter, perPage } = this.state;
   let library = Library;
   console.log(library)

  if (filter !== "") {
  library = library.filter(item =>
  item.hotelinfo.hotelsearch.realname.toLowerCase().includes(filter)
 );
 }

 library = _.chunk(library, perPage);
 this.setState({
  library,
  currentPage: 1,
  maxPage: library.length === 0 ? 1 : library.length
   });
  };

// Previous Page
   previousPage = () =>
   this.setState(prevState => ({
    currentPage: prevState.currentPage - 1
   }));

// Next Page
  nextPage = () =>
   this.setState(prevState => ({
   currentPage: prevState.currentPage + 1
  }));

 // handle filter
  handleFilter = evt =>
  this.setState(
   {
    filter: evt.target.value.toLowerCase()
  },
   () => {
   this.reorganiseLibrary();
  }
 );

// handle per page
   handlePerPage = (evt) =>
   this.setState({
    perPage: evt.target.value 
    }, () => this.reorganiseLibrary());

  // handle render of library
    renderLibrary = () => {
    const { library, currentPage } = this.state;
    if (!library || (library && library.length === 0)) {
     return <div>No results</div>;
     }
    return library[currentPage - 1].map(item => (
    <div key={item.hotelinfo.hotelsearch.realname}>
     {item.hotelinfo.hotelsearch.realname}</div>
      ));
    };

   render() {
    const { library, currentPage, perPage, maxPage } = this.state;
    return (
    <div className="library">
     <h1>Library</h1>
     <div className="d-flex">
      <div className="flex-fill">
          <label className="library__filter-label">Filter</label>
          <input value={this.state.filter} onChange={this.handleFilter} />
        </div>
      <div className="flex-fill text-right">
          <label className="library__per-page-label">Per page</label>
          <input placeholder="per page" value={this.state.perPage} onChange={this.handlePerPage} />
      </div>
  </div>
  <div className="library__book-shelf">
      {this.renderLibrary()}
  </div>
  <div className="d-flex">
      <div className="flex-fill">
        {currentPage !== 1 && (
          <button onClick={this.previousPage}>Previous</button>
        )}
      </div>
      <div className="flex-fill text-right">
        {(currentPage < maxPage) && (
          <button onClick={this.nextPage}>Next</button>
        )}
      </div>
  </div>
  <div className="library__page-info text-right">
      {this.state.currentPage} of {this.state.maxPage}
  </div>
      </div>)}};
       ReactDOM.render(<App/>, document.getElementById('root')); 

Upvotes: 0

Views: 2038

Answers (2)

Win
Win

Reputation: 5594

You're overcomplicating the logic. Here's an example I've written below and an explanation.

Step 1: We load our library data using an reorganise library function that is ran when the component is mounted, when the filter changes or the per page value is edited.

Step 2: This function will calculate the chunks of the library by splitting them by a perPage value and then calculates the max page value and sets the list of books into the library state.

Step 3: When the filter is changed, we execute an additional piece of code in our function that just filters the books based on a string includes which will just filter our books so that only the books that are matched are set into our library state.

const Library = [
  {
    name: "Star Wars"
  },
  {
    name: "Harry Potter"
  },
  {
    name: "Lord of the Rings"
  },
  {
    name: "Star Trek"
  },
  {
    name: "The Fault in Our Stars"
  },
  {
    name: "Number the Stars"
  },
  {
    name: "Blue"
  },
  {
    name: "Act Da Fool"
  },
  {
    name: "The Gilded Cage"
  },
  {
    name:
      "To Get to Heaven First You Have to Die (Bihisht faqat baroi murdagon)"
  },
  {
    name: "Lebanon"
  },
  {
    name: "Tenderness"
  },
  {
    name: "It"
  },
  {
    name: "Locked Out (Enfermés dehors)"
  },
  {
    name: "Waterloo Bridge"
  },
  {
    name: "Set It Off"
  },
  {
    name: "Nil By Mouth"
  },
  {
    name: "Monte Carlo"
  },
  {
    name: "Treasure of the Four Crowns"
  },
  {
    name: "Donnie Darko"
  },
  {
    name: "Cry-Baby"
  },
  {
    name: "Juan of the Dead (Juan de los Muertos)"
  },
  {
    name: "Constant Nymph, The"
  }
];

// Main App Component
class App extends React.Component {
  state = {
    library: null,
    perPage: 3,
    currentPage: 1,
    maxPage: null,
    filter: ""
  };

  componentDidMount() {
    this.reorganiseLibrary();
  }
  
  // Calculates the library
  reorganiseLibrary = () => {
    const { filter, perPage } = this.state;
    let library = Library;

    if (filter !== "") {
      library = library.filter(book =>
        book.name.toLowerCase().includes(filter)
      );
    }

    library = _.chunk(library, perPage);

    this.setState({
      library,
      currentPage: 1,
      maxPage: library.length === 0 ? 1 : library.length
    });
  };

  // Previous Page
  previousPage = () =>
    this.setState(prevState => ({
      currentPage: prevState.currentPage - 1
    }));

  // Next Page
  nextPage = () =>
    this.setState(prevState => ({
      currentPage: prevState.currentPage + 1
    }));
    
  // handle filter
  handleFilter = evt =>
    this.setState(
      {
        filter: evt.target.value.toLowerCase()
      },
      () => {
        this.reorganiseLibrary();
      }
    );
    
  // handle per page
  handlePerPage = (evt) =>
    this.setState({
      perPage: evt.target.value 
    }, () => this.reorganiseLibrary());

  // handle render of library
  renderLibrary = () => {
    const { library, currentPage } = this.state;
    if (!library || (library && library.length === 0)) {
      return <div>No results</div>;
    }
    return library[currentPage - 1].map(book => (
      <div key={book.name}>{book.name}</div>
    ));
  };

  render() {
    const { library, currentPage, perPage, maxPage } = this.state;
    return (
      <div className="library">
          <h1>Library</h1>
          <div className="d-flex">
              <div className="flex-fill">
                  <label className="library__filter-label">Filter</label>
                  <input value={this.state.filter} onChange={this.handleFilter} />
              </div>
              <div className="flex-fill text-right">
                  <label className="library__per-page-label">Per page</label>
                  <input placeholder="per page" value={this.state.perPage} onChange={this.handlePerPage} />
              </div>
          </div>
          <div className="library__book-shelf">
              {this.renderLibrary()}
          </div>
          <div className="d-flex">
              <div className="flex-fill">
                {currentPage !== 1 && (
                  <button onClick={this.previousPage}>Previous</button>
                )}
              </div>
              <div className="flex-fill text-right">
                {(currentPage < maxPage) && (
                  <button onClick={this.nextPage}>Next</button>
                )}
              </div>
          </div>
          <div className="library__page-info text-right">
              {this.state.currentPage} of {this.state.maxPage}
          </div>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.library {
  padding: 10px;
  margin: 10px;
  font-family: sans-serif;
}

.library__filter-label, .library__per-page-label {
  margin-right: 10px;
  text-transform: uppercase;
  font-size: 11px;
  font-weight: bold;
}

.library__book-shelf {
  padding: 10px;
  margin: 10px 0;
  border: 1px solid black;
} 

.library__page-info {
  margin-top: 20px;
}

.d-flex {
  display: flex;
}

.flex-fill {
  flex: 1 1;
}

.text-right {
  text-align: right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<div id="root"></div>

Upvotes: 1

Steve
Steve

Reputation: 1864

One possible solution would be to have separate state values for data (which you already have) and filteredData. Run your pagination on filteredData, not data. When a user changes any filter other than page number, reset state.currentPage to 1 so a blank result is never seen.

When the user selects a new filter, the change of state.filteredData should also reset your pagination correctly, as it will redraw your pageNumbers, resulting in renderPageNumbers being redrawn as well.

Another solution would be to include pagination results on the API itself instead of the UI, which would allow for more robust use of your information. But if you don't have control over the API, of course that's not a helpful solution.

Upvotes: 0

Related Questions