gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.29k stars 10.31k forks source link

Js-Search and Routing #24703

Closed ubuntutest closed 4 years ago

ubuntutest commented 4 years ago

Hello,

I have implemented js-search on my site, it works correctly and I am having a lot of fun https://github.com/gatsbyjs/gatsby/blob/master/docs/docs/adding-search-with-js-search.md

I have added the link tag on each element of the research, I am trying to implement routing but without success:

...
        createPage({
          path: `/page-2/${data.id}`,
          component: path.resolve(`./src/templates/ClientSearchTemplate.js`),
          context: {
            bookData: {
              allBooks: data,
              options: {
                indexStrategy: `Prefix match`,
                searchSanitizer: `Lower Case`,
                TitleIndex: true,
                AuthorIndex: true,
                SearchByTerm: true,
              },
            },
          },
        })
....
...
              {queryResults.map(item => {
                  return (
                    <Link key={item.id} to={`/page-2/${item.id}`}>
                    <tr key={`row_${item.id}`}>
                      <td
                        style={{
                          fontSize: "14px",
                          border: "1px solid #d3d3d3",
                        }}
                      >
                        {item.id}
                      </td>
                      <td
                        style={{
                          fontSize: "14px",
                          border: "1px solid #d3d3d3",
                        }}
                      >
                        {item.title}
                      </td>
                      <td
                        style={{
                          fontSize: "14px",
                          border: "1px solid #d3d3d3",
                        }}
                      >
                        {item.body}
                      </td>
                    </tr>
                    </Link>
                  )
                })}
...

I'm stuck and not able to go on, I think I miss the structure of the page-2.js file, I don't understand how it should be implemented.

Anyone have an opensource example I can learn from? I understood the mechanism using graphql, but in this case without graphql I don't know how.

Thanks

jonniebigodes commented 4 years ago

@ubuntutest i've picked up on the issue you're faced with. I was the author of said piece of documentation and i would like to help you out in solving your issue if i can. If you don't mind can you make a reproduction of your code following these steps so that i can take a better look at and possibly solve your issue.

Feel free to provide feedback

LekoArts commented 4 years ago

Thank you for opening this!

Since I don't see a bug report or something similar here I'll close this. Please refer to our communities about general React questions or feel free to continue the discussion here.

ubuntutest commented 4 years ago

Hi @LekoArts, thanks for the clarification, I write here to the developer, as he answered my request.

Hi @jonniebigodes thanks for the help you offer me, I'm sure it's my lack of knowledge of react and gatsby. I uploaded the code to Github: https://github.com/ubuntutest/js-search-example-2

I completely removed my project and loaded the js-search module with minor changes.

My goal is to solve the routing for each link. It would also be interesting to use Search in the index instead of /search.

At the moment if you connect to the localhost:8000/search page and click on a link, this searches for a page that does not exist, routing does not work.

I am not asking to do the job for me, but only a suggestion of how to proceed.

Thank you.

jonniebigodes commented 4 years ago

@ubuntutest i've checked your repo and i see where the problem lies. Going to detail the steps i took to triage this. Breaking this into two parts as you're asking two different things.

I've changed gatsby-node.js to the following:

const path = require("path")
const axios = require("axios")
exports.createPages = ({ actions }) => {
  const { createPage } = actions
  return new Promise((resolve, reject) => {
    axios
      .get("https://bvaughn.github.io/js-search/books.json")
      .then(result => {
        const { data } = result

        /**
         * generates a new array with a new element called link, which contains a parsed string based on the title.
         * Sorry about the big regex expression and the replace usage, you might need to adjust accordingly to your case and further
         * streamline it
         */
         const parsedData= data.books.map(book=>{
           return {
             ...book,
             link:`/${book.title.replace(/[^A-Za-z0-9\s]/g,"").replace(/\s{2,}/g, " ").replace(/ /g,"-").toLowerCase()}/`
           }
         })

        /**
         * iterates over the array above and generates the pages accordingly.
         * usage of foreach is due to the fact that we're not changing the array, simply transversing it
         * 
         */
        parsedData.forEach(item => {
          createPage({
            path:item.link,
            component:require.resolve(`./src/templates/BookInformationTemplate.js`),
            context:{
              bookInfo:item
            }
          })
        });
        /**
         * creates a dynamic page with the data received
         * injects the data into the context object alongside with some options
         * to configure js-search
         */
        createPage({
          path: "/search",
          component: path.resolve(`./src/templates/ClientSearchTemplate.js`),
          context: {
            bookData: {
              /* allBooks: data.books, */
              allBooks:parsedData,
              options: {
                indexStrategy: "Prefix match",
                searchSanitizer: "Lower Case",
                TitleIndex: true,
                AuthorIndex: true,
                SearchByTerm: true,
              },
            },
          },
        })
        resolve()
      })
      .catch(err => {
        console.log("====================================")
        console.log(`error creating Page:${err}`)
        console.log("====================================")
        reject(new Error(`error on page creation:\n${err}`))
      })
  })
}

Key thing to take from this. The code that is present in the documentation and in your repo does not make reference or allow any page creation other than the /search endpoint. You'll need to create a page for each item present in the dataset that is returned from the axios.get(). Hence why the parsedData was added. It will go over each item and add a new element called link, with a string that is formatted to be able to used by Gatsby and generated a page (sorry about the long replace and regular expression, you can fine tune it to your needs afterwards). Afterwards the Gatsby createPage hook is put into play and generates a page based on the above and some information about the book.

import React from 'react'

const BookInformationTemplate=props=>{
    const {pageContext}= props
    const {bookInfo}= pageContext

    return(
        <div>
            <h1>This is a page with the information about the following book</h1>
            <div>
                <h2>{bookInfo.isbn}</h2>
                <h2>{bookInfo.title}</h2>
                <h3>by: {bookInfo.author}</h3>
            </div>
        </div>
    )
}

export default BookInformationTemplate

Nothing to "exotic" (pardon the bad pun once again) just a stateless functional component to display the book information that is supplied by Gatsby's special prop context.

Also took a look at src\components\ClientSearch in your repo and made a small tweak to the render function as you can see below:

render() {
    const { searchResults, searchQuery } = this.state
    const { books } = this.props
    const queryResults = searchQuery === "" ? books : searchResults
    return (
      <div>
        <div style={{ margin: "0 auto" }}>
          <form onSubmit={this.handleSubmit}>
            <div style={{ margin: "0 auto" }}>
              <label htmlFor="Search" style={{ paddingRight: "10px" }}>
                Enter your search here
              </label>
              <input
                id="Search"
                value={searchQuery}
                onChange={this.searchData}
                placeholder="Enter your search here"
                style={{ margin: "0 auto", width: "400px" }}
              />
            </div>
          </form>
          <div>
            Number of items:
            {queryResults.length}
            <table
              style={{
                width: "100%",
                borderCollapse: "collapse",
                borderRadius: "4px",
                border: "1px solid #d3d3d3",
              }}
            >
              <thead style={{ border: "1px solid #808080" }}>
                <tr>
                  <th
                    style={{
                      textAlign: "left",
                      padding: "5px",
                      fontSize: "14px",
                      fontWeight: 600,
                      borderBottom: "2px solid #d3d3d3",
                      cursor: "pointer",
                    }}
                  >
                    Book ISBN
                  </th>
                  <th
                    style={{
                      textAlign: "left",
                      padding: "5px",
                      fontSize: "14px",
                      fontWeight: 600,
                      borderBottom: "2px solid #d3d3d3",
                      cursor: "pointer",
                    }}
                  >
                    Book Title
                  </th>
                  <th
                    style={{
                      textAlign: "left",
                      padding: "5px",
                      fontSize: "14px",
                      fontWeight: 600,
                      borderBottom: "2px solid #d3d3d3",
                      cursor: "pointer",
                    }}
                  >
                    Book Author
                  </th>
                </tr>
              </thead>
              <tbody>
                {queryResults.map(item => {
                  return (
                    <tr key={`row_${item.isbn}`}>
                      <td
                        style={{
                          fontSize: "14px",
                          border: "1px solid #d3d3d3",
                        }}
                      >
                        {item.isbn}
                      </td>

                      <td
                        style={{
                          fontSize: "14px",
                          border: "1px solid #d3d3d3",
                        }}
                      > <Link key={item.isbn} to={item.link}> {item.title}</Link> 
                      </td>
                      <td
                        style={{
                          fontSize: "14px",
                          border: "1px solid #d3d3d3",
                        }}
                      >
                        {item.author}
                      </td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    )
  }

Check how <Link> is used in comparaison with what you have. With this small tweak you won't get semantic issues in the console.

Issued gatsby develop to generate a development build and waited for it to go through. Opened up localhost:8000/search and i'm presented with:

ubuntu_1

Check the bottom of the picture and you'll see something like http://localhost:8000/hellenistic-history-and-culture/. Clicking the item with the title Hellenistic history and culture presents the following:

ubuntu_2

Search wise you don't need to be concerned about. You can search as you did before, the results will be filtered based on what you search and you have the link available aswell.

Regarding this:

It would also be interesting to use Search in the index instead of /search.

Do you mean adding the search functionality inside the index page. If that's the case you can easily achieve it by changing the index into the following (i'm going to turn it into a React Component as i don't know your degree of knowledge with React and hooks and also with this you get a familiar codebase that is used in the documentation in question, when you get more acquainted you can turn this easily into a functional hook based component).

Inside src/pages/index.js:

import React, { Component } from "react";
import axios from "axios";
import ClientSearch from "../components/ClientSearch";
class IndexPage extends Component {
  state = {
    isLoading: true,
    isError: false,
    bookInformation: [],
  };
  componentDidMount() {
    axios
      .get("https://bvaughn.github.io/js-search/books.json")
      .then((result) => {
        const { data } = result;
        const parsedData = data.books.map((book) => {
          return {
            ...book,
            link: `/${book.title
              .replace(/[^A-Za-z0-9\s]/g, "")
              .replace(/\s{2,}/g, " ")
              .replace(/ /g, "-")
              .toLowerCase()}/`,
          };
        });
        this.setState({ isLoading: false, bookInformation: parsedData });
      })
      .catch((err) => {
        console.log(err);
        this.setState({ isLoading: false, isError: true });
      });
  }
  render() {
    // destructures the state
    const { isLoading, isError, bookInformation } = this.state;

    // beased on the state values it will show something
    if (isLoading) {
      return (
        <div>
          <h2>Fetching the results! Hold please</h2>
        </div>
      );
    }
    if (isError) {
      return (
        <div>
          <h2>Oh no!!!! Something bad happened</h2>
        </div>
      );
    }
    return (
      <div>
        <h2>homepage with search component</h2>
        <ClientSearch
          books={
            bookInformation
          } /* adds the parsed data obtained when the component was mounted(see componentDidMount) */
          engine={{
            indexStrategy: "Prefix match",
            searchSanitizer: "Lower Case",
            TitleIndex: true,
            AuthorIndex: true,
            SearchByTerm: true,
          }} /* adds the same set of options that are provided in `gatsby-node` */
        />
      </div>
    );
  }
}
export default IndexPage;

What is happening with this piece of code is the following, once the page/component is mounted the books will be fetched same like is happening in gatsby-node.js and the link is being added to allow the routing/navigation to work. I left in a loading and error state variable as a means to display something while the data is being fetched and parsed and in case there's some error.

Issuing gatsby develop and opening up http://localhost:8000 will show the loading text for a bit and then the following:

ubuntu_3

Don't forget that if you don't want to get a dedicated path/endpoint you can comment out or delete the following inside your gatsby-node.js

 createPage({
          path: "/search",
          component: path.resolve(`./src/templates/ClientSearchTemplate.js`),
          context: {
            bookData: {
              /* allBooks: data.books, */
              allBooks:parsedData,
              options: {
                indexStrategy: "Prefix match",
                searchSanitizer: "Lower Case",
                TitleIndex: true,
                AuthorIndex: true,
                SearchByTerm: true,
              },
            },
          },
        })

Sorry for the extremely long post, but i wanted to give you a more detailed explanation on how you could achieve what you intend to. And don't worry about your React knowledge, it comes with time and practice and also we are all here to learn.....

Feel free to provide feedback.

PS:

@LekoArts why did you close this? This is not a bug report, but a question regarding a piece of documentation for this framework. The author posted a sample code just to give some insights and was asking if it's possible to achieve what he/she wants to achieve using this framework and the package in question and the documentation.

I'm aware that you like all the remainder elements of the Gatsby team are busy people as we all are. But before closing issues left and right taking a "draconian" (pardon the bad pun) approach on issue triage, probably start off by leaving in the customary message asking for a reproduction and depending on your workload take a look at the reproduction supplied by the author or leave it in for someone in the community like me or someone else to take a look at it and possibly help the author and with that help out someone that is learning about this framework and also about the underlying packages.

Seriously this is not the first time i've seen this and it worries me that this type of behaviour happens quite often. With that instead of bringing in the community, it's pushing them back.

Also one thing to consider is that the author might have asked around in the communities and came up dry hence why i resorted to coming here to the repo and ask for help?

ubuntutest commented 4 years ago

@jonniebigodes I can only thank you for this help. I never expected such a detailed and simply complete answer, you left me no other doubts or problems.

You did for me what was my goal, I can only learn a lot from all this.

Also I'd like to say that I really agree with you on what you said at the end of the post.

I am really enjoying React and even more the Gatsby Framework for the web, I sincerely hope that it will grow a lot with time.

I'm sure my problem was mostly due to the lack of knowledge, I should have studied more, but surely a good community can help study, make it more pleasant, interesting and fast.

I study as much as I can in my spare time, but not having much time to devote to reading and understanding any type of documentation, I probably should have abandoned this plugin and or language and I would have had to look for another solution.

Before writing here I searched a lot on the web, but still not much is found about some of the themes of React and Gatsby.

So I thank you doubly, I really enjoyed it.

LekoArts commented 4 years ago

@jonniebigodes Thanks for pointing this out and sorry to @ubuntutest if that made you feel uncomfortable. We're currently trying out new processes around issue triaging and in this instance I should have followed another route and asked for more information first. We're also working on a better place for questions than on GitHub but until then I'll take extra care to think about this. Thanks for the lengthy explanation and thanks for using Gatsby! :)

jonniebigodes commented 4 years ago

@ubuntutest no need to thank whatsoever, just glad that i was able to help you as much as i could. And don't worry about it, just keep practicing and reading at your own pace and in no time you'll be more than able to build awesome stuff either with Gatsby or React in general. One thing that the Gatsby excels is for easy to follow and extremely comprehensive, covering a lot of topics. Circling back to the documentation and looking at it now, probably i could have made a more advanced case and could have added it, for that mishap i'm trully sorry.

@LekoArts no problem whatsoever, i'm glad that you're actively working on new processes to triage issues and a better place for questions like for instance this one. And like i mentioned to @ubuntutest no need to thank, just glad that i was able to help him solve his issue.

Both of you stay safe.