· 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