rubicon44TechBlog
18 results
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!

© 2025, rubicon44TechBlog All rights reserved.