아직 백엔드 api가 없기 때문에 일단 로컬 데이터를 가지고 구현을 하였다.

props driling을 방지하기 위해서 redux-tollkit을 사용하여 구현하였고 새로고침을 하였을 때에도 검색한 결과가 초기화 되지 않고 유지 되게 하기 위해서 react-router-dom의 useSearchParams를 사용하였다.

추후에 백엔드 api가 구현되면 api를 이용하여 다시 구현할 예정이다.

 

1. 검색창 구현

src/search/components/Searchbar.tsx

import React, { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';

import { AppDispatch } from '../../store';
import { productsActions } from '../../store/slice/products';
import { Product } from '../../common/types/products';
import { data } from '../../electronicProducts/page/Electronics';
import Button from '../../common/components/Button';

const Searchbar: React.FC = () => {
  const [searchText, setSearchText] = useState('');
  const [searchParams, setSearchParams] = useSearchParams();
  const dispatch = useDispatch<AppDispatch>();
  const navigate = useNavigate();

  const searchedProducts = data.filter((product: Product) => {
    const query = searchParams.get('product') || '';
    return product.title.toLowerCase().includes(query.toLowerCase());
  });

  const inputChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearchText(event.target.value);
  };

  const searchHandler = () => {
    navigate('/search');
    setSearchParams({ product: searchText });
    dispatch(productsActions.getProducts(searchedProducts));
  };

  useEffect(() => {
    dispatch(productsActions.getProducts(searchedProducts));
  }, [dispatch, searchedProducts]);

  return (
    <Container>
      <SearchBar
        type='text'
        value={searchText}
        onChange={inputChangeHandler}
        placeholder='검색어를 입력하세요.'
      />
      <Button
        text={'검색'}
        click={searchHandler}
        bgcolor={'blueviolet'}
        color={'#fff'}
        textsize={'18px'}
        margin={'0'}
        padding={'4px'}
      />
    </Container>
  );
};

export default Searchbar;

const Container = styled.div`
  display: flex;
  align-items: center;
`;

const SearchBar = styled.input`
  margin: 1px;
  height: 25px;
`;

먼저 새로고침이 되었을 때도 검색어로 입력된 텍스트를 query에 유지하기 위하여 useSearchParams로 검색어를 query에 설정하게 구현 하였다.

그러고 나서 데이터에 filter배열함수를 이용하여 query에 설정된 검색어를 상품명으로 가지고 있는 제품만을 필터하게끔 하고 필터된 데이터를 dispatch하여 state를 업데이트 하게 하게 해주었다.

 

2. 검색 상품 목록 페이지

src/search/page/SearchProducts.tsx

import React from 'react';
import { useSelector } from 'react-redux';

import Products from '../../common/components/products/Products';
import { RootState } from '../../store';

const SearchProducts: React.FC = () => {
  const products = useSelector((state: RootState) => state.products.products);

  return <Products products={products} />;
};

export default SearchProducts;

검색된 상품들을 나열해주는 페이지를 새로 만들어 주었다 검색된 상품 데이터는 useSelecter를 이용하여 받아 온 뒤 기존의 Products컴포넌트를 재사용하여 데이터를 props로 전달해주므로써 검색된 상품이 페이지에 목록으로 렌더링하게끔 하였다.

 

src/common/components/products/Products.tsx

import React from 'react';
import styled from 'styled-components';

import { Product } from '../../types/products';
import ProductCard from './ProductCard';

type Props = {
  products: Product[] | undefined;
};

const Products: React.FC<Props> = ({ products }) => {
  return (
    <ProductList>
      {products?.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </ProductList>
  );
};

export default Products;

const ProductList = styled.ul`
  display: grid;
  padding: 16px 24px;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
`;

 

src/common/components/products/ProductCard.tsx

import React from 'react';
import styled from 'styled-components';
import { Product } from '../../types/products';

type Props = {
  product: Product;
};

const ProductCard: React.FC<Props> = ({
  product: { category, image, price, title },
}) => {
  return (
    <Card>
      <img src={image} alt={title} />
      <div>
        <h3>{title}</h3>
        <p>{`₩${price}`}</p>
      </div>
      <p>{category}</p>
    </Card>
  );
};

export default ProductCard;

const Card = styled.li`
  & img {
    width: 100%;
  }
  cursor: pointer;
`;

+ Recent posts