Skip to content

Instantly share code, notes, and snippets.

@vvo
Created July 21, 2016 07:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vvo/6eab06c20aa2620f038292ebc1f84350 to your computer and use it in GitHub Desktop.
Save vvo/6eab06c20aa2620f038292ebc1f84350 to your computer and use it in GitHub Desktop.
Algolia + React
<h1>algoliasearch-helper-js + react-algoliasearch-helper + React</h1>
<div id="root"></div>
const client = algoliasearch('latency', '6be0576ff61c053d5f9a3225e2a90f76');
const helper = algoliasearchHelper(client, 'movies', {
disjunctiveFacets: ['genre']
});
const Provider = reactAlgoliaSearchHelper.Provider;
const connect = reactAlgoliaSearchHelper.connect;
const SearchBox = connect()(
({helper}) =>
<div className="search-box">
<div className="search-box__button">
<i className="fa fa-search"></i>
</div>
<div className="search-box__input-container">
<input
type="text"
className="search-box__input"
placeholder="Search here"
onChange={e => helper.setQuery(e.target.value).search()}
/>
</div>
</div>
);
const getHighlighted = s => ({__html: s});
const Rating = ({rating}) => {
const bem = block('rating');
const stars = [];
for (var i = 1; i <= 5; ++i) {
stars.push(i <= rating);
}
stars.reverse();
return <div className={bem()}>
{stars.map(
(active, starIndex) => <span key={starIndex} className={bem("star")({active})}>☆</span>
)}
</div>
}
const Hit = ({
_highlightResult: {
title: {
value: title
}
},
year,
image,
rating,
genre: genres
}) =>
<article className="movie">
<img className="movie__image" src={image} />
<div className="movie__meta">
<div className="movie__title">
<span dangerouslySetInnerHTML={getHighlighted(title)} />
<span className="movie__year">{year}</span>
</div>
<div className="movie__rating"><Rating rating={rating} /></div>
<div className="movie__genres">
{genres.map(genre => <div key={genre} className="movie__genre">{genre}</div>)}
</div>
</div>
</article>
const Hits = connect(
state => ({results: state.searchResults})
)(
({results}) => {
if (!results) return <div className="hits__empty">Search for something</div>;
if (results.hits.length === 0) return <div className="hits__empty">No results</div>;
return <div className="hits">
{results.hits.map(hit => <div className="movie__container" key={hit.objectID}><Hit {...hit}/></div>)}
</div>;
}
);
const Genres = connect(
state => ({genres: state.searchResults && state.searchResults.getFacetValues('genre') || []})
)(
({genres, helper}) =>
<div>
{genres.map(
({name, count, isRefined}) =>
<div key={name} onClick={e => helper.toggleRefine('genre', name).search()} className={`facet__item facet__item${isRefined ? '_active': ''}`}>
<div className="facet__item-label">{name} <div className="facet__item-count">{count}</div></div>
</div>
)}
</div>
);
const Pagination = connect(
({searchResults}) => (searchResults === null ? {page: 0, nbPages: 0} : {page: searchResults.page, nbPages: searchResults.nbPages})
)(
({page, nbPages, helper}) =>
<div className="pagination">
<button className="pagination__button" onClick={e => helper.setPage(page - 1).search()} disabled={page === 0}>Previous</button>
<span className="pagination__page">{page + 1}</span>
<button className="pagination__button" onClick={e => helper.setPage(page + 1).search()} disabled={page + 1 >= nbPages}>Next</button>
</div>
);
const App = () =>
<Provider helper={helper}>
<div className="container">
<SearchBox/>
<div className="content">
<div className="facets">
<div className="facet">
<div className="facet__title">Genre</div>
<Genres/>
</div>
</div>
<div className="canvas">
<Hits/>
<Pagination/>
</div>
</div>
</div>
</Provider>;
ReactDOM.render(<App/>, document.querySelector('#root'));
helper.search();
<script src="https://npmcdn.com/react@15.0.1/dist/react.js"></script>
<script src="https://npmcdn.com/react-dom@15.0.1/dist/react-dom.js"></script>
<script src="https://npmcdn.com/react-algoliasearch-helper@1/umd/reactAlgoliaSearchHelper.js"></script>
<script src="https://npmcdn.com/algoliasearch@3/dist/algoliasearchLite.js"></script>
<script src="https://npmcdn.com/algoliasearch-helper@2/dist/algoliasearch.helper.js"></script>
<script src="https://npmcdn.com/bem-cn@2"></script>
.rating {
unicode-bidi: bidi-override;
direction: rtl;
text-align: left;
}
.rating__star {
display: inline-block;
position: relative;
width: 1.1em;
height: 1.1em;
color: #cccccc;
}
.rating__star_active,
.rating__star_active ~ .rating__star {
color: transparent;
}
.rating__star_active:before {
content: "\2605";
position: absolute;
left: 0;
color: gold;
}
* {
box-sizing: border-box;
}
body {
font-family: arial, sans-serif;
font-size: 20px;
margin: 0;
padding: 20px;
}
.container {
display: flex;
flex-direction: column;
}
.search-box {
position: relative;
display: flex;
}
.search-box__button {
align-self: stretch;
display: flex;
align-items: center;
justify-content: center;
width: 2.4em;
font-size: 1.6em;
background-color: lighten(#0074D9, 5%);
color: white;
cursor: pointer;
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
box-shadow: inset 0 -6px 12px -8px rgba(0, 0, 0, 0.2), inset 0 6px 12px -8px rgba(255, 255, 255, 0.2);
&:hover {
background-color: lighten(#0074D9, 10%);
}
&:active {
background-color: darken(#0074D9, 5%);
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.2);
}
}
.search-box__input-container {
position: relative;
flex: 1;
}
.search-box__input {
width: 100%;
font-size: inherit;
border: 1px solid rgba(0, 0, 0, 0.3);
border-left: none;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.1);
outline: none;
padding: 0.8em 1em;
font-size: 1.2em;
&:focus {
box-shadow: inset 0 2px 6px rgba(0, 100, 220, 0.15);
border-color: rgba(0, 100, 220, 0.6);
}
}
.content {
display: flex;
margin-top: 1rem;
border: 1px solid rgba(0, 0, 0, 0.3);
}
.canvas {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
padding: 10px;
background-color: rgba(0, 0, 0, 0.1);
box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.1);
}
.hits {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.hits__empty {
height: 100%;
justify-content: center;
align-items: center;
font-size: 1.6em;
color: rgba(0, 0, 0, 0.4);
}
.movie__container {
flex: 1 0 25%;
min-width: 380px;
}
.movie {
position: relative;
display: flex;
margin: 10px;
border: 1px solid rgba(0, 0, 0, 0.4);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
background-color: white;
padding: 0.2em;
}
.movie__image {
flex-shrink: 0;
height: 231px;
width: 154px;
background-color: rgba(0, 0, 0, 0.1);
}
.movie__meta {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 1em;
}
.movie__title {
align-items: center;
font-size: 1.1em;
font-weight: bold;
em {
color: rgb(0, 150, 230);
background: rgba(0, 160, 220, 0.2);
}
}
.movie__year {
font-size: 0.8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.6);
padding-left: .5em;
}
.movie__rating {
margin-top: 0.2em;
}
.movie__genres {
display: flex;
flex-wrap: wrap;
font-size: 0.8em;
margin-top: 0.7em;
margin-right: -0.4em;
margin-bottom: -0.4em;
}
.movie__genre {
color: rgba(0, 0, 0, 0.7);
padding: 0.4em 0.6em;
background-color: rgba(0, 0, 0, 0.1);
margin-right: 0.4em;
margin-bottom: 0.4em;
}
.pagination {
margin: 10px 0;
text-align: center;
}
.pagination > * {
margin: 0 .5em;
}
.pagination__button {
font-size: 110%;
}
.facets {
width: 20%;
min-width: 270px;
border-right: 1px solid rgba(0, 0, 0, 0.3);
}
.facet {
padding: 1em 0;
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
}
.facet__title {
padding: 0 1em;
font-size: 1.1em;
margin-bottom: 0.6em;
}
.facet__item {
padding: 0 1em;
cursor: pointer;
color: rgba(0, 0, 0, 0.7);
&:hover {
color: black;
background-color: rgba(0, 0, 0, 0.04);
}
}
.facet__item_active {
background-color: rgba(0, 0, 0, 0.08);
font-weight: bold;
color: black;
}
.facet__item-label {
position: relative;
display: flex;
line-height: 1.4em;
cursor: inherit;
}
.facet__item-count{
top: 0;
position: absolute;
right: 0;
font-size: 0.8em;
color: rgba(0, 0, 0, 0.4);
}
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment