Created
September 25, 2022 20:15
-
-
Save raphaelobene/f0ba6ab2d255e61c80271a31f550b7b0 to your computer and use it in GitHub Desktop.
Search, filter and Paginate with ReactJs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<body> | |
<noscript>You need to enable JavaScript to run this app.</noscript> | |
<div id="root"></div> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*, | |
*: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