· 4 years ago · Apr 09, 2021, 07:00 AM
1import React, { useCallback, useEffect, useMemo, useState } from "react";
2import { useHistory } from "react-router-dom";
3import {
4  Button,
5  Container,
6  FormControl,
7  InputGroup,
8  Row,
9  Col,
10} from "react-bootstrap";
11import qs from "query-string";
12import Sidebar from "./Sidebar";
13import DefaultLayoutStyles from "components/layouts/default/DefaultLayout.module.scss";
14import productApi from "api/productApi";
15import ProductFeed from "components/product/ProductFeed/ProductFeed";
16import ProductFeedSkeleton from "components/product/ProductFeedSkeleton/ProductFeedSkeleton";
17
18const Index = () => {
19  const history = useHistory();
20  const queryParams = useMemo(() => qs.parse(history.location.search), [
21    history.location.search,
22  ]);
23
24  const [products, setProducts] = useState([]);
25  const [loading, setLoading] = useState(false);
26  const [search, setSearch] = useState(queryParams.q || "");
27  const [filter, setFilter] = useState(queryParams.filter || "new");
28  const [selectedCategory, setSelectedCategory] = useState(
29    queryParams.category || null
30  );
31  const [page, setPage] = useState(parseInt(queryParams.page) || null);
32  const [isLastPage, setIsLastPage] = useState(false);
33
34  const fetchProducts = useCallback(async () => {
35    setLoading(true);
36
37    const api = {
38      new: productApi.all,
39      popular: productApi.popular,
40      random: productApi.random,
41    };
42
43    try {
44      const res = await api[filter](queryParams);
45      const { data, links } = res.data;
46
47      setProducts((prev) => [...prev, ...data]);
48
49      if (!links.next) {
50        setIsLastPage(true);
51      }
52    } catch (err) {
53      alert("Failed to fetch products data.");
54    } finally {
55      setLoading(false);
56    }
57  }, [queryParams, filter]);
58
59  useEffect(() => {
60    fetchProducts();
61  }, [fetchProducts]);
62
63  // Watch search
64  useEffect(() => {
65    if (queryParams.q !== search) {
66      const searchTimeout = setTimeout(() => {
67        const params = {
68          ...queryParams,
69          q: search,
70        };
71
72        history.push({
73          pathname: window.location.pathname,
74          search: qs.stringify(params),
75        });
76
77        setProducts([]);
78        setPage(null);
79        setIsLastPage(false);
80      }, 400);
81
82      return () => clearTimeout(searchTimeout);
83    }
84  }, [search, history, queryParams]);
85
86  // Watch Page
87  useEffect(() => {
88    if (page !== null && page !== queryParams.page) {
89      const params = {
90        ...queryParams,
91        page,
92      };
93
94      history.push({
95        pathname: window.location.pathname,
96        search: qs.stringify(params),
97      });
98    }
99  }, [page, history, queryParams]);
100
101  // Watch selected category
102  // useEffect(() => {
103  //   if (!queryParams.category && !selectedCategory) return;
104  //   if (queryParams.category === selectedCategory) return;
105
106  //   const params = {
107  //     ...queryParams,
108  //   };
109
110  //   selectedCategory
111  //     ? (params.category = selectedCategory)
112  //     : delete params.category;
113
114  //   setProducts([]);
115  //   setPage(null);
116  //   setIsLastPage(false);
117
118  //   history.push({
119  //     pathname: window.location.pathname,
120  //     search: qs.stringify(params),
121  //   });
122  // }, [selectedCategory, queryParams, history]);
123
124  return (
125    <Container className={DefaultLayoutStyles.page}>
126      <Row>
127        <Col lg={3} className='mb-3 mb-lg-0'>
128          <Sidebar onSelect={setSelectedCategory} />
129        </Col>
130
131        <Col lg={9}>
132          {/* Filter & Search */}
133          <div className='d-flex justify-content-between align-items-center mb-3'>
134            {/* Filter */}
135            <div>
136              <FormControl
137                as='select'
138                value={filter}
139                onChange={(e) => setFilter(e.target.value)}
140              >
141                <option value='new'>New</option>
142                <option value='popular'>Popular</option>
143                <option value='random'>Random</option>
144              </FormControl>
145            </div>
146
147            {/* Search */}
148            <div>
149              <InputGroup>
150                <FormControl
151                  value={search}
152                  placeholder='Search by name'
153                  onChange={(e) => setSearch(e.target.value)}
154                ></FormControl>
155
156                <InputGroup.Append>
157                  <InputGroup.Text>
158                    <i className='mdi mdi-magnify'></i>
159                  </InputGroup.Text>
160                </InputGroup.Append>
161              </InputGroup>
162            </div>
163          </div>
164
165          {/* Skeletons */}
166          {loading && !products.length && (
167            <Row>
168              {[1, 2, 3, 4, 5, 6].map((i) => (
169                <Col key={i} xs={6} lg={4}>
170                  <ProductFeedSkeleton />
171                </Col>
172              ))}
173            </Row>
174          )}
175
176          {/* Product List */}
177          {!loading && products.length > 0 && (
178            <Row>
179              {products.map((product) => (
180                <Col key={product.id} xs={6} lg={4} className='mb-3'>
181                  <ProductFeed product={product} />
182                </Col>
183              ))}
184            </Row>
185          )}
186
187          {/* Show More Btn */}
188          {!isLastPage && products.length > 0 && (
189            <div className='d-flex justify-content-center'>
190              <Button
191                disabled={loading}
192                onClick={() => setPage(page ? page + 1 : 2)}
193              >
194                <i className='mdi mdi-eye mr-2'></i>
195                Show More
196              </Button>
197            </div>
198          )}
199        </Col>
200      </Row>
201    </Container>
202  );
203};
204
205export default Index;
206