Skip to content

Instantly share code, notes, and snippets.

@raphaelobene
Created September 25, 2022 20:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raphaelobene/f0ba6ab2d255e61c80271a31f550b7b0 to your computer and use it in GitHub Desktop.
Save raphaelobene/f0ba6ab2d255e61c80271a31f550b7b0 to your computer and use it in GitHub Desktop.
Search, filter and Paginate with ReactJs
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
import React from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";
import { useState, useEffect } from "https://cdn.skypack.dev/react";
// Note: the empty deps array [] means
// this useEffect will run once
function App() {
const [error, setError] = useState(null);
const [loaded, setLoaded] = useState(false);
const [items, setItems] = useState([]);
const [query, setQuery] = useState("");
const [filter, setFilter] = useState("");
const [paginate, setpaginate] = useState(8);
useEffect(() => {
// const request_headers = new Headers();
// const api_key = null;
// request_headers.append("Authorization", `Bearer ${api_key}`);
// request_headers.append("Content-Type", "application/json");
// const request_options = {
// method: "GET",
// headers: request_headers
// };
fetch(
"https://raw.githubusercontent.com/iamspruce/search-filter-painate-reactjs/main/data/countries.json"
)
.then((res) => res.json())
.then(
(result) => {
setLoaded(true);
setItems(result);
},
(error) => {
setLoaded(true);
setError(error);
}
);
}, []);
const data = Object.values(items);
const search_parameters = Object.keys(Object.assign({}, ...data));
const filter_items = [...new Set(data.map((item) => item.region))];
function search(items) {
return items.filter(
(item) =>
item.region.includes(filter) &&
search_parameters.some((parameter) =>
item[parameter].toString().toLowerCase().includes(query)
)
);
}
const load_more = (event) => {
setpaginate((prevValue) => prevValue + 8);
};
if (error) {
return <>{error.message}</>;
} else if (!loaded) {
return <>loading...</>;
} else {
return (
<div className="wrapper">
<div className="search-wrapper">
<label htmlFor="search-form">
<input
type="search"
name="search-form"
id="search-form"
className="search-input"
placeholder="Search for..."
onChange={(e) => setQuery(e.target.value)}
/>
<span className="sr-only">Search countries here</span>
</label>
<div className="select">
<select
onChange={(e) => setFilter(e.target.value)}
className="custom-select"
aria-label="Filter Countries By Region"
>
<option value="">Filter By Region</option>
{filter_items.map((item) => (
<option value={item}>Filter By {item}</option>
))}
</select>
<span className="focus"></span>
</div>
</div>
<ul className="card-grid">
{search(data)
.slice(0, paginate)
.map((item) => (
<li key={item.alpha3Code}>
<article className="card">
<div className="card-image">
<img
src={item.flag.large}
alt={item.name}
/>
</div>
<div className="card-content">
<h2 className="card-name">
{item.name}
</h2>
<ol className="card-list">
<li>
population:{" "}
<span>{item.population}</span>
</li>
<li>
Region:{" "}
<span>{item.region}</span>
</li>
<li>
Capital:{" "}
<span>{item.capital}</span>
</li>
</ol>
</div>
</article>
</li>
))}
</ul>
<button onClick={load_more}>Load More</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
*,
*:after,
*:before {
margin: 0;
padding: 0;
}
.sr-only {
border: 0 !important;
clip: rect(1px, 1px, 1px, 1px) !important;
-webkit-clip-path: inset(50%) !important;
clip-path: inset(50%) !important;
height: 1px !important;
margin: -1px !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
width: 1px !important;
white-space: nowrap !important;
}
img {
width: 100%;
}
:root {
--bg: hsl(0, 0%, 98%);
--bg-offset: hsl(0, 0%, 100%);
--text: hsl(200, 15%, 8%);
--gray: hsl(0, 0%, 52%);
--border: rgba(0, 0, 0, 0.1);
}
ul,
ol {
list-style: none;
}
body {
background: var(--bg);
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 14px;
}
.wrapper {
width: 96%;
max-width: 1140px;
margin: 0 auto;
}
.wrapper-inner {
max-width: 600px;
text-align: center;
margin-left: auto;
margin-right: auto;
}
.card-grid {
margin: 2em 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 48px;
}
.card {
background-color: var(--bg-offset);
padding: 0px;
box-shadow: 0px 2px 4px var(--border);
transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
}
.card:hover {
transform: scale(1.1);
}
.card:hover .card-content h2 {
display: block;
-webkit-line-clamp: none;
-webkit-box-orient: none;
overflow: visible;
}
.card-image {
max-height: 150px;
overflow: hidden;
}
.card-image img {
margin-top: -13px;
min-height: 100%;
width: 100%;
object-fit: cover;
object-position: center;
}
.card-content {
padding: 32px 15px;
}
.card-content h2 {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-list {
margin-top: 16px;
}
.card-list li {
color: var(--text);
margin-top: 8px;
}
.card-list li span {
color: var(--gray);
}
/* search input */
.search-wrapper {
margin: 48px 0;
display: flex;
justify-content: space-between;
}
@media (max-width: 375px) {
.search-input {
width: 100%;
}
.search-wrapper {
justify-content: start;
flex-wrap: wrap;
}
.select {
margin-top: 3.5em;
}
}
.search-input {
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0xNS44NTMgMTYuNTZjLTEuNjgzIDEuNTE3LTMuOTExIDIuNDQtNi4zNTMgMi40NC01LjI0MyAwLTkuNS00LjI1Ny05LjUtOS41czQuMjU3LTkuNSA5LjUtOS41IDkuNSA0LjI1NyA5LjUgOS41YzAgMi40NDItLjkyMyA0LjY3LTIuNDQgNi4zNTNsNy40NCA3LjQ0LS43MDcuNzA3LTcuNDQtNy40NHptLTYuMzUzLTE1LjU2YzQuNjkxIDAgOC41IDMuODA5IDguNSA4LjVzLTMuODA5IDguNS04LjUgOC41LTguNS0zLjgwOS04LjUtOC41IDMuODA5LTguNSA4LjUtOC41eiIvPjwvc3ZnPg==");
background-color: var(--bg-offset);
background-size: 16px 16px;
background-position: left 10px center;
background-repeat: no-repeat;
padding: 1.4em 2em;
padding-left: 2.7em;
border: 1px solid var(--border);
color: var(--gray);
box-shadow: 0px 4px 6px var(--border);
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.search-input:hover {
box-shadow: 0px 0px 0px var(--border);
}
/* select from moderncss.dev */
select {
appearance: none !important;
outline: none;
background-color: var(--bg-offset);
border-radius: 0.25em;
border-width: 1px;
border-style: solid;
border-color: var(--border);
padding: 1.4em 2em 1.4em 1em;
margin: 0;
margin-right: 1em;
width: 100%;
font-family: inherit;
font-size: inherit;
cursor: inherit;
line-height: inherit;
width: 100%;
color: var(--gray);
}
.select {
min-width: 15ch;
max-width: 30ch;
cursor: pointer;
line-height: 1.1;
background-color: transparent;
display: grid;
grid-template-areas: "select";
align-items: center;
position: relative;
box-shadow: 0px 4px 6px var(--border);
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
}
.select:hover {
box-shadow: 0px 0px 0px var(--border);
}
.select::after {
content: "";
display: block;
width: 0.8em;
height: 0.5em;
background-color: var(--text);
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
justify-self: end;
margin-right: 1em;
}
select,
.select:after {
grid-area: select;
}
select:focus + .focus {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border: 2px solid var(--text);
border-radius: inherit;
}
button {
display: block;
margin-top: 2rem;
margin-bottom: 3rem;
font-size: 1.4rem;
padding: 12px 32px;
margin-left: auto;
margin-right: auto;
border-radius: 40px;
background-color: #00bb00;
border: 1px solid #00aa00;
color: #fff;
cursor: pointer;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment