Search code examples
reactjsgraphqlgatsbyalgolia

stucked in the post preview component


I'm stucked, lost... I'm using gatsby-plugin-algolia, when I search, the empty fields are filtered. They are generated by the PostPreview component.

enter image description here

my archive.js in templates folder (to list blog posts):

imports....

// algolia search
import algoliasearch from 'algoliasearch/lite'
import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-dom'
import PostPreview from '../components/postPreview/PostPreview'

const searchClient = algoliasearch(
  'MY KEY...',
  'MY KEY...'
)

const archiveTemplate = ({
  data: { allWpPost },
  pageContext: { catId, catName, catURI, categories, numPages, currentPage }
}) => {
  return (
    <Layout>
      <Wrapper>
        <BreadCrumbs
          parent={{
            uri: '/blog/todos-os-posts',
            title: 'Blog'
          }}
        />
        <ContentWrapper>
          <ArchiveSidebar catId={catId} categories={categories.edges} />
          <PageContent>
            <InstantSearch searchClient={searchClient} indexName="Gatsby">
              <SearchBox />
              <Hits hitComponent={PostPreview} />
            </InstantSearch>

            <h1 dangerouslySetInnerHTML={{ __html: catName }} />
            {allWpPost.edges.map(post => (
              <article key={post.node.id} className="entry-content">
                <StyledDate
                  dangerouslySetInnerHTML={{ __html: post.node.date }}
                />
                <Link to={`/blog${post.node.uri}`}>
                  <StyledH2
                    dangerouslySetInnerHTML={{ __html: post.node.title }}
                  />
                </Link>
                <p dangerouslySetInnerHTML={{ __html: post.node.excerpt }}></p>
                <StyledReadMore to={`/blog${post.node.uri}`}>
                  Leia +
                </StyledReadMore>
                <div className="dot-divider" />
              </article>
            ))}
            <Pagination
              catUri={catURI} // from the context
              page={currentPage}
              totalPages={numPages}
            />
          </PageContent>
        </ContentWrapper>
      </Wrapper>
    </Layout>
  )
}

export default archiveTemplate

export const pageQuery = graphql`
  query($catId: String!, $skip: Int!, $limit: Int!) {
    allWpPost(
      filter: { categories: { nodes: { elemMatch: { id: { eq: $catId } } } } }
      skip: $skip
      limit: $limit
    ) {
      edges {
        node {
          id
          title
          excerpt
          uri
          slug
          date(formatString: "DD, MMMM, YYYY", locale: "pt")
        }
      }
    }
  }
`

My Algolia in Gatsby-config.js:

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`
})

const AlgoliaBloghQuery = `
{
  allWpPost {
    nodes {
      title
      excerpt
      id
    }
  }
}

`

Works:

enter image description here

My PostPreview component, here I'm lost.

imports...

const Postpreview = ({ id, title, date, excerpt, uri }) => {
  return (
    <div>
      <article key={id} className="entry-content">
        <StyledDate dangerouslySetInnerHTML={{ __html: date }} />
        <Link to={`/blog${uri}`}>
          <StyledH2 dangerouslySetInnerHTML={{ __html: title }} />
        </Link>
        <p dangerouslySetInnerHTML={{ __html: excerpt }}></p>
        <StyledReadMore to={`/blog${uri}`}>Leia +</StyledReadMore>
        <div className="dot-divider" />
      </article>
    </div>
  )
}

export default Postpreview

I don't know how to get the data to pass to the props, I don't know if the queries are conflicting (archive.js - gatsby-config). Only read more button are rendered.


Solution

  • It's a broad question where multiple things can go wrong, I will try to answer the most common use case but it will need some effort on your side to debug and find the bug(s).

    A few insights before

    When dealing with Algolia + Gatsby, you are delegating to Algolia the responsibility of paginating, show/hide/render all certain posts, etc. So the following part, is redundant, should be removed:

            {allWpPost.edges.map(post => (
              <article key={post.node.id} className="entry-content">
                <StyledDate
                  dangerouslySetInnerHTML={{ __html: post.node.date }}
                />
                <Link to={`/blog${post.node.uri}`}>
                  <StyledH2
                    dangerouslySetInnerHTML={{ __html: post.node.title }}
                  />
                </Link>
                <p dangerouslySetInnerHTML={{ __html: post.node.excerpt }}></p>
                <StyledReadMore to={`/blog${post.node.uri}`}>
                  Leia +
                </StyledReadMore>
                <div className="dot-divider" />
              </article>
            ))}
            <Pagination
              catUri={catURI} // from the context
              page={currentPage}
              totalPages={numPages}
            />
    

    Algolia will show all posts by default if you are not searching anything (initial state).

    Regarding the pagination, you have the Pagination component from React InstantSearch, so you don't need to worry about it, Algolia handles it for you.

    Everything that is related to Algolia's search must be wrapped inside InstantSearch component, otherwise, it will never work.


    Said that, your Searchbox needs to refine the Algolia's result and should look like:

    import React from "react"
    import { connectSearchBox } from "react-instantsearch-dom"
    import { Search as SearchIcon } from "@styled-icons/fa-solid"
    
    export default connectSearchBox(
      ({ refine, currentRefinement, className, onFocus }) => (
        <form className={className}>
          <input
            className="SearchInput"
            type="text"
            placeholder="Search"
            aria-label="Search"
            onChange={e => refine(e.target.value)}
            value={currentRefinement}
            onFocus={onFocus}
          />
          <SearchIcon className="SearchIcon" />
        </form>
      )
    )
    

    Be aware of:

        onChange={e => refine(e.target.value)}
        value={currentRefinement}
    

    Those are key lines and, if anything is not working as expected, try to print the values in a console.log.

    Here basically you are "connecting" the component to Algolia's API by connectSearchBox HOC, which belongs to Algolia; it exposes the current search string as currentRefinement and a function for changing it called refine.

    I don't know how to get the data to pass to the props, I don't know if the queries are conflicting (archive.js - gatsby-config). Only read more button are rendered.

    The results, are called "hits" and what you are receiving as props is a hit, so, your PostPreview should look like:

    const Postpreview = ({ hit }) => {
      console.log(hit);
      let { id, title, date, excerpt, uri } = hit;
     return (
        <div>
          <article key={id} className="entry-content">
            <StyledDate dangerouslySetInnerHTML={{ __html: date }} />
            <Link to={`/blog${uri}`}>
              <StyledH2 dangerouslySetInnerHTML={{ __html: title }} />
            </Link>
            <p dangerouslySetInnerHTML={{ __html: excerpt }}></p>
            <StyledReadMore to={`/blog${uri}`}>Leia +</StyledReadMore>
            <div className="dot-divider" />
          </article>
        </div>
      )
    }
    
    export default Postpreview
    

    If the rest of the components are ok, you will get the hit property which will contain all the needed data so you need to destructure it to get the data. Debug as needed, adding as many logs as needed.

    You will find a detailed example (could be improved) in Gatsby's docs where the key components are:

    import { Link } from "gatsby"
    import { default as React } from "react"
    import {
      connectStateResults,
      Highlight,
      Hits,
      Index,
      Snippet,
      PoweredBy,
    } from "react-instantsearch-dom"
    
    const HitCount = connectStateResults(({ searchResults }) => {
      const hitCount = searchResults && searchResults.nbHits
    
      return hitCount > 0 ? (
        <div className="HitCount">
          {hitCount} result{hitCount !== 1 ? `s` : ``}
        </div>
      ) : null
    })
    
    const PageHit = ({ hit }) => (
      <div>
        <Link to={hit.slug}>
          <h4>
            <Highlight attribute="title" hit={hit} tagName="mark" />
          </h4>
        </Link>
        <Snippet attribute="excerpt" hit={hit} tagName="mark" />
      </div>
    )
    
    const HitsInIndex = ({ index }) => (
      <Index indexName={index.name}>
        <HitCount />
        <Hits className="Hits" hitComponent={PageHit} />
      </Index>
    )
    
    const SearchResult = ({ indices, className }) => (
      <div className={className}>
        {indices.map(index => (
          <HitsInIndex index={index} key={index.name} />
        ))}
        <PoweredBy />
      </div>
    )
    
    export default SearchResult
    

    Since Algolia supports multiple indices, the SearchResult iterates over all indices and displays hits for each of them using the HitsInIndex component. It, in turn, relies heavily on the Hits component from the InstantSearch library.

    The PageHit component is responsible for displaying a single page (“hit”) in a search result.

    connectStateResults wraps components to provide them with details about the current search such as the query, the number of results and timing statistics.

    Highlight and Snippet both display attributes of matching search results to the user. The former renders the full value whereas the latter only shows a snippet. A snippet is the text immediately surrounding the match. The attribute property is the name of the key in the Algolia index (as generated by pageToAlgoliaRecord in algolia-queries.js).

    Overall, it seems that your page is rendering the correct amount the Postpreview component but is not rendering the correct content because of the destructure of hit.