rubicon44TechBlog
    Search by

    Implement search function on my own.

    created_at:June 04, 2022

    updated_at:June 04, 2022

    Today, I am going to explain how to implement search function on my own.

    Purposes of this article creation

    • To be able to create function on my own.

    Way of thinking

    • Create search API in back-end
    • Create search bar in front-end
      • search input column
      • search model select(User, Task)
      • search target select(perfect, partial)
    • Call search API in front-end
    • Return search results to front-end in back-end
    • Display search results in front-end

    Implementation

    Prerequisite

    • To use Docker and docker-compose.
    • To create User and Task model.

    ※Since some other functions have already been created, we have omitted sections that are not relevant to the content of this article.

    【docker-compose.yml】
    version: '3'
    services:
      front:
        build: .
        volumes:
          - .:/grow
          - node_modules:/grow/node_modules
        command: sh -c "yarn start"
        ports:
         - "3001:3000"
        tty: true
    
    volumes:
      node_modules:
    【Dockerfile】
    FROM node:16-alpine
    
    RUN mkdir /grow
    ENV FRONT_ROOT /grow
    WORKDIR $FRONT_ROOT
    
    COPY ./package.json $FRONT_ROOT/package.json
    COPY ./node_modules $FRONT_ROOT/node_modules
    
    RUN npm install -g n && yarn install
    RUN npm install -g create-react-app
    RUN npm install --silent
    RUN npm install --save dotenv
    
    ADD . $FRONT_ROOT
    【db/schema.rb】
    ActiveRecord::Schema.define(version: 2022_06_01_132549) do
    
    ・・・Abbr.・・・
    
      create_table "tasks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
        t.string "title", null: false
        t.text "content"
        t.datetime "created_at", precision: 6, null: false
        t.datetime "updated_at", precision: 6, null: false
        t.string "user_id"
        t.integer "status"
        t.string "start_date"
        t.string "end_date"
      end
    
      create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
        t.string "nickname"
        t.datetime "created_at", precision: 6, null: false
        t.datetime "updated_at", precision: 6, null: false
        t.string "email"
        t.string "firebase_id"
        t.text "bio"
        t.string "user_name"
        t.index ["user_name"], name: "index_users_on_user_name", unique: true
      end
    
    ・・・Abbr.・・・
    
    end
    【config/routes.rb】
    Rails.application.routes.draw do
      # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
    
      post "/users", to: "users#create"
      get "/users/:id", to: "users#show"
      put "/users/:id", to: "users#update"
      post "/users/sign_in", to: "sessions#create"
    
      resources :tasks do
    ・・・Abbr.・・・
      end
    
      resources :users, only: [:show] do
    ・・・Abbr.・・・
      end
    
    ・・・Abbr.・・・
    
      resources :searches, only: [:index]
    end

    Create search bar and call search API and display search results in front-end

    【src/components/containers/pages/search/index.jsx】
    import React from 'react';
    import { SearchTemplate } from '../../templates/search';
    
    export function SearchIndex() {
      return <SearchTemplate />;
    }
    【src/components/containers/templates/search/index.jsx】
    import React from 'react';
    import styled from 'styled-components';
    import { Header } from '../../organisms/header';
    import { SearchList } from '../../organisms/search/searchList';
    
    const Main = styled.main`
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      padding: 30px 10px;
      text-align: center;
      background-color: #f8f7f3;
    `;
    
    export function SearchTemplate() {
      return (
        <>
          <Header />
          <Main>
            <SearchList />
          </Main>
        </>
      );
    }
    【src/components/containers/organisms/search/searchList.jsx】
    import React, { useState } from 'react';
    import { Link } from 'react-router-dom';
    import styled from 'styled-components';
    import { Title } from '../../../presentational/atoms/Title/title';
    import { BackButton } from '../../../presentational/atoms/Button/backButton';
    import { getSearches } from '../../../../infra/api';
    
    const ListCover = styled.div`
      position: relative;
      min-width: 180px;
      margin-top: 30px;
    `;
    
    const ListHeader = styled.div`
      display: flex;
      width: 100%;
    
      > h2 {
        width: 100%;
        margin-right: 45px;
      }
    `;
    
    const FormCover = styled.div`
      min-width: 260px;
      padding: 0 10px;
      text-align: left;
    `;
    
    const List = styled.div`
      display: flex;
      align-items: center;
      text-align: left;
      padding-bottom: 10px;
      border-bottom: 1px solid #ddd;
    
      &:not(:first-of-type) {
        margin-top: 10px;
      }
    `;
    
    export function SearchList() {
      const [load, setLoad] = useState(false);
      const [searchResults, setSearchResults] = useState([]);
    
      const handleSubmit = (e) => {
        e.preventDefault();
        setLoad(true);
        const { contents } = e.target.elements;
        const { model } = e.target.elements;
        const { method } = e.target.elements;
        const searchData = { contents: contents.value, model: model.value, method: method.value };
        let isMounted = true;
        getSearches(searchData)
          .then((response) => {
            if (isMounted) setSearchResults(response.data.results);
          })
          .catch();
        // .catch(() => {
        // });
        setLoad(false);
        return () => {
          isMounted = false;
        };
      };
    
      return (
        <>
          <ListHeader>
            <BackButton />
            <Title title="Search List" />
          </ListHeader>
          <div>
            <FormCover>
              <form onSubmit={handleSubmit}>
                <label htmlFor="contents">
                  <input name="contents" type="contents" placeholder="Contents" />
                </label>
                <select name="model">
                <option value="user">User</option>
                  <option value="task">Task</option>
                </select>
                <select name="method">
                  <option value="perfect">Perfect</option>
                  <option value="partial">Partial</option>
                </select>
                <button type="submit" disabled={load}>
                  Search
                </button>
              </form>
            </FormCover>
            <ListCover>
              {searchResults.map((result) => (
                <>
                  {result.nickname && (
                    <List>
                      nickname:
                      <Link to={`/users/${result.id}`}>{result.nickname}</Link>
                    </List>
                  )}
                  {result.title && (
                    <List>
                      title:
                      <Link to={`/users/${result.user_id}/tasks/${result.id}`}>{result.title}</Link>
                      <div>cotent:{result.content}</div>
                    </List>
                  )}
                </>
              ))}
            </ListCover>
          </div>
        </>
      );
    }
    【src/infra/api.js】
    import axios from 'axios';
    
    ・・・Abbr.・・・
    
    // searches
    export const getSearches = (params) =>
      axios({
        method: 'get',
        url: `/searches`,
        params,
      });
    【src/App.js】
    import React, { useState, useEffect } from 'react';
    import styled from 'styled-components';
    
    ・・・Abbr.・・・
    
    // Search
    import { SearchIndex } from './components/containers/pages/search';
    
    ・・・Abbr.・・・
    
    export function App() {
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        // setTimeout(() => setLoading(false), 1000)
        setLoading(false);
    
        if (
          localStorage.getItem('token') === '' ||
          localStorage.getItem('token') === null ||
          Date.now() >= jwtDecode(localStorage.getItem('token')).exp * 1000
        ) {
          signOut(auth);
        }
      }, []);
    
      if (loading) {
        return <div>Loading...</div>;
      }
      return (
        <Wrapper>
          <PageWrapper>
            <AuthProvider>
              <Router>
                <Routes>
    
                 ・・・Abbr.・・・
    
                  {/* Searches */}
                  <Route
                    exact
                    path="/search"
                    element={<PrivateRoute element={<SearchIndex />} />}
                  />
                </Routes>
              </Router>
            </AuthProvider>
          </PageWrapper>
        </Wrapper>
      );
    }

    Create search API and return search results to front-end in back-end

    【app/controllers/searches_controller.rb】
    class SearchesController < ApplicationController
      def index
        @contents = params[:contents]
        @model = params[:model]
        @method = params[:method]
        @results = search_for(@model, @contents, @method)
    
        render json: { results: @results }
      end
    
      private
      def search_for(model, contents, method)
        if model == 'user'
          if method == 'perfect'
            User.where(name: contents)
          else
            User.where('name LIKE ?', '%'+contents+'%')
          end
        elsif model == 'task'
          if method == 'perfect'
            Task.where(title: contents)
            Task.where(content: contents)
          else
            Task.where('title LIKE ?', '%'+contents+'%')
            Task.where('content LIKE ?', '%'+contents+'%')
          end
        end
      end
    end

    My code

    References

    Summary/What I learned this time

    This time, I had a pretty good understanding of how to call APIs using React and RailsAPI. I would like to implement more and more functions on my own, from simple to difficult ones! I might be able to do implementations on my own than I thought! It gave me confidence!

    © 2022, rubicon44TechBlog All rights reserved.