Skip to content

Commit

Permalink
- Combine Redux Reducers in Every Reducers File to Make The File Cont…
Browse files Browse the repository at this point in the history
…ains One Reducer(Like allCategories and category Reducers Becomes One Reducer Which is category)

1) Refactor The Main itemReducer Shared File
2) Refactor and Reduce Redux Types in Redux Types Folder
3) Refactor and Reduce Redux Actions in Redux Actions Folder
4) Refactor rootReducer.js File
5) Edit The Reflected Changes Because The Previous Refactoring in The Whole Project

- Enable Loading on Mounting The Pages That Uses useGetAllItems Hook
  • Loading branch information
Nabil-Nasr committed Aug 21, 2023
1 parent 0f0d7c4 commit 2659c13
Show file tree
Hide file tree
Showing 32 changed files with 214 additions and 235 deletions.
2 changes: 1 addition & 1 deletion src/components/Brand/BrandCardsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useSelector } from "react-redux";


const BrandCardsContainer = ({ title, btnTitle, btnPath }) => {
const { data: brands, loading, error } = useSelector(({ allBrands }) => allBrands);
const { itemsData: brands, loading, error } = useSelector(({ brand }) => brand);

return (
<div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Category/CategoryCardsContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useSelector } from "react-redux";
const backgroundColors = ["#FFCCBC", "#F4DBA5", "#55CFDF", "#2196F3", "#FFD3E8"];

const CategoryCardsContainer = ({ title, btnTitle, btnPath }) => {
const { data: categories, loading, error } = useSelector(({ allCategories }) => allCategories);
const { itemsData: categories, loading, error } = useSelector(({ category }) => category);

return (
<div className="d-grid row-gap-3">
Expand Down
8 changes: 4 additions & 4 deletions src/components/utils/CustomSelect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import Select from "react-select";
import debounce from "../../utils/debounce";


const CustomSelect = ({ isMulti, placeholder, onInputChange, allItemsReducer, name, onSelect, disableCache, ...props }) => {
const { data, loading, error = "" } = useSelector((state) => state[allItemsReducer]);
const CustomSelect = ({ isMulti, placeholder, onInputChange, itemReducer, name, onSelect, disableCache, ...props }) => {
const { itemsData, loading, error } = useSelector((state) => state[itemReducer]);
const [cacheInputValue, setCacheInputValue] = useState("");
const cacheObjectRef = useRef({});
const cacheData = cacheObjectRef.current[cacheInputValue] || data;
const cacheData = cacheObjectRef.current[cacheInputValue] || itemsData;

// saving debounce invokes
const memoizedOnInputChange = useMemo(() => debounce(inputValue => {
Expand All @@ -20,7 +20,7 @@ const CustomSelect = ({ isMulti, placeholder, onInputChange, allItemsReducer, na
}
// regular expression error happens when the user types a special character
if (!error || error?.includes("Regular expression")) {
cacheObjectRef.current[prev] = data;
cacheObjectRef.current[prev] = itemsData;
}
}
return inputValue;
Expand Down
21 changes: 13 additions & 8 deletions src/hooks/useGetAllItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@ import { useDispatch, useSelector } from "react-redux";
import { useSearchParams } from "react-router-dom";
import { useEffect } from "react";

const useGetAllItems = ({ allItemsReducer, getAllItemsAction }) => {
const useGetAllItems = ({ itemReducer, getAllItems, itemEnableLoading }) => {
const limit = 7;
const dispatch = useDispatch();

useEffect(() => {
dispatch(itemEnableLoading());
}, []);

const [searchParams, setSearchParams] = useSearchParams({ page: 1 });
const page = searchParams.get("page");
useEffect(() => {
const controller = new AbortController();
dispatch(getAllItems({ params: { limit, page, fields: "name,image" }, signal: controller.signal }));
return () => controller.abort();
}, [page]);

const applyPagination = (page) => {
setSearchParams((searchParams) => {
Expand All @@ -14,14 +25,8 @@ const useGetAllItems = ({ allItemsReducer, getAllItemsAction }) => {
});
};

const { paginationResult: { currentPage, numberOfPages } = {} } = useSelector(state => state[allItemsReducer]);
const { paginationResult: { currentPage, numberOfPages } } = useSelector(state => state[itemReducer]);

const page = searchParams.get("page");
useEffect(() => {
const controller = new AbortController();
dispatch(getAllItemsAction({ params: { limit, page, fields: "name,image" }, signal: controller.signal }));
return () => controller.abort();
}, [page]);

return { numberOfPages, applyPagination, currentPage };
};
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useGetItemsWithParams.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useEffect } from "react";
import { useDispatch } from "react-redux";

const useGetItemsWithParams = ({ params = {}, getAllItemsAction, useEffectHook = useEffect }) => {
const useGetItemsWithParams = ({ params = {}, getAllItems, useEffectHook = useEffect }) => {
const dispatch = useDispatch();
// can be replaced with useUpdateEffect
useEffectHook(() => {
const controller = new AbortController();
dispatch(getAllItemsAction({ params, signal: controller.signal }));
dispatch(getAllItems({ params, signal: controller.signal }));
return () => {
controller.abort();
};
Expand Down
12 changes: 6 additions & 6 deletions src/pages/Admin/AdminAddProductPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ const AdminAddProductPage = () => {
const [categoryId, setCategoryId] = useState("");

const fields = "name";
useGetItemsWithParams({ params: { limit: 10, keyword: categoryKeyword, fields }, getAllItemsAction: getAllCategories });
useGetItemsWithParams({ params: { limit: 10, keyword: categoryKeyword, fields }, getAllItems: getAllCategories });

// useUpdateEffect here is required because of the initial categoryId is empty string which is not valid
useGetItemsWithParams({ params: { limit: 10, keyword: subCategoryKeyword, category: categoryId, fields }, getAllItemsAction: getAllSubCategories, useEffectHook: useUpdateEffect });
useGetItemsWithParams({ params: { limit: 10, keyword: subCategoryKeyword, category: categoryId, fields }, getAllItems: getAllSubCategories, useEffectHook: useUpdateEffect });

useGetItemsWithParams({ params: { limit: 10, keyword: brandKeyword, fields }, getAllItemsAction: getAllBrands });
useGetItemsWithParams({ params: { limit: 10, keyword: brandKeyword, fields }, getAllItems: getAllBrands });

const [images, setImages] = useState({});
const appendFormData = async formData => {
Expand Down Expand Up @@ -66,7 +66,7 @@ const AdminAddProductPage = () => {

<CustomSelect
placeholder="التصنيف الرئيسي"
allItemsReducer="allCategories"
itemReducer="category"
onInputChange={setCategoryKeyword}
onSelect={({ value }) => setCategoryId(value)}
name="category"
Expand All @@ -75,7 +75,7 @@ const AdminAddProductPage = () => {
<CustomSelect
isMulti
placeholder={!categoryId ? "التصنيفات الفرعية (إختر تصنيف رئيسي أولا)" : "التصنيفات الفرعية"}
allItemsReducer="allSubCategories"
itemReducer="subCategory"
onInputChange={setSubCategoryKeyword}
name="subcategory"
// disabling cache to avoid showing the same options when main category changes
Expand All @@ -84,7 +84,7 @@ const AdminAddProductPage = () => {

<CustomSelect
placeholder="الماركة"
allItemsReducer="allBrands"
itemReducer="brand"
onInputChange={setBrandKeyword}
name="brand"
/>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Admin/AdminAddSubcategoryPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import AdminAddRawData from "../../components/Admin/AdminAddRawData";

const AdminAddSubcategoryPage = () => {
const [keyword, setKeyword] = useDebouncedState("", 500);
useGetItemsWithParams({ params: { limit: 10, keyword, fields: "name" }, getAllItemsAction: getAllCategories });
useGetItemsWithParams({ params: { limit: 10, keyword, fields: "name" }, getAllItems: getAllCategories });

return (
<AdminAddRawData formAction={createSubCategory} pageHeader="إضافة تصنيف فرعي جديد">

<CustomSelect placeholder="التصنيف الرئيسي" onInputChange={setKeyword} allItemsReducer="allCategories" name="category" />
<CustomSelect placeholder="التصنيف الرئيسي" onInputChange={setKeyword} itemReducer="category" name="category" />

<input type="text" name="name" placeholder="إسم التصنيف الفرعي" className="p-2 form-control rounded-0" />

Expand Down
8 changes: 4 additions & 4 deletions src/pages/Brands/BrandsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { Container } from "react-bootstrap";
import Pagination from "../../components/utils/Pagination/Pagination";
import BrandCardsContainer from "../../components/Brand/BrandCardsContainer";
import useGetAllItems from "../../hooks/useGetAllItems";
import { getAllBrands } from "../../redux/actions/brandActions";
import { brandEnableLoading, getAllBrands } from "../../redux/actions/brandActions";

const BrandsPage = () => {
const {numberOfPages, applyPagination, currentPage } = useGetAllItems({allItemsReducer:"allBrands",getAllItemsAction:getAllBrands});
const { numberOfPages, applyPagination, currentPage } = useGetAllItems({ itemReducer: "brand", getAllItems: getAllBrands, itemEnableLoading: brandEnableLoading });

return (
<Container className="my-4">
<BrandCardsContainer title="كل الماركات"/>
<BrandCardsContainer title="كل الماركات" />
<Pagination pageCount={numberOfPages} applyPagination={applyPagination} currentPage={currentPage} />
</Container>
);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Categories/CategoriesPage.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Container } from "react-bootstrap";
import CategoryCardsContainer from "../../components/Category/CategoryCardsContainer";
import Pagination from "../../components/utils/Pagination/Pagination";
import { getAllCategories } from "../../redux/actions/categoryActions";
import { categoryEnableLoading, getAllCategories } from "../../redux/actions/categoryActions";
import useGetAllItems from "../../hooks/useGetAllItems";

const CategoriesPage = () => {
const { numberOfPages, applyPagination, currentPage } = useGetAllItems({allItemsReducer:"allCategories",getAllItemsAction:getAllCategories});
const { numberOfPages, applyPagination, currentPage } = useGetAllItems({ itemReducer: "category", getAllItems: getAllCategories, itemEnableLoading: categoryEnableLoading });

return (
<Container className="my-4">
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/HomePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { getAllCategories } from "../../redux/actions/categoryActions";
import { getAllBrands } from "../../redux/actions/brandActions";

const HomePage = () => {
useGetItemsWithParams({ getAllItemsAction: getAllCategories, params: { limit: 6, fields: "name,image" } });
useGetItemsWithParams({ getAllItemsAction: getAllBrands, params: { limit: 6, fields: "name,image" } });
useGetItemsWithParams({ getAllItems: getAllCategories, params: { limit: 3, fields: "name,image" } });
useGetItemsWithParams({ getAllItems: getAllBrands, params: { limit: 6, fields: "name,image" } });
return (
<>
<Slider />
Expand Down
10 changes: 4 additions & 6 deletions src/redux/actions/brandActions.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { ALL_BRANDS_ENABLE_LOADING, BRAND_ENABLE_LOADING, CREATE_BRAND, CREATE_BRAND_ERROR, GET_ALL_BRANDS, GET_ALL_BRANDS_ERROR } from "../types/brandTypes";
import { BRAND_ENABLE_LOADING, BRAND_ERROR, CREATE_BRAND, GET_ALL_BRANDS } from "../types/brandTypes";
import createFormDataItem from "./utils/createFormDataItem";
import getAllItems from "./utils/getAllItems";


export const allBrandsEnableLoading = () => ({ type: ALL_BRANDS_ENABLE_LOADING });

export const getAllBrands = getAllItems({ url: "/api/v1/brands", GET_ALL_ITEMS: GET_ALL_BRANDS, GET_ALL_ITEMS_ERROR: GET_ALL_BRANDS_ERROR,allItemsEnableLoadingAction:allBrandsEnableLoading });


export const brandEnableLoading = () => ({ type: BRAND_ENABLE_LOADING });

export const createBrand = createFormDataItem({ url: "/api/v1/brands", CREATE_ITEM: CREATE_BRAND, CREATE_ITEM_ERROR: CREATE_BRAND_ERROR,itemEnableLoadingAction:brandEnableLoading });
export const getAllBrands = getAllItems({ url: "/api/v1/brands", GET_ALL_ITEMS: GET_ALL_BRANDS, ITEM_ERROR: BRAND_ERROR, itemEnableLoading: brandEnableLoading });

export const createBrand = createFormDataItem({ url: "/api/v1/brands", CREATE_ITEM: CREATE_BRAND, ITEM_ERROR: BRAND_ERROR, itemEnableLoading: brandEnableLoading });

14 changes: 4 additions & 10 deletions src/redux/actions/categoryActions.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { CREATE_CATEGORY, CREATE_CATEGORY_ERROR, ALL_CATEGORIES_ENABLE_LOADING, CATEGORY_ENABLE_LOADING, GET_ALL_CATEGORIES, GET_ALL_CATEGORIES_ERROR } from "../types/categoryTypes";
import { CATEGORY_ENABLE_LOADING, CATEGORY_ERROR, CREATE_CATEGORY, GET_ALL_CATEGORIES } from "../types/categoryTypes";
import createFormDataItem from "./utils/createFormDataItem";
import getAllItems from "./utils/getAllItems";




export const allCategoriesEnableLoading = () => ({ type: ALL_CATEGORIES_ENABLE_LOADING });


export const getAllCategories = getAllItems({ url: "/api/v1/categories", GET_ALL_ITEMS: GET_ALL_CATEGORIES, GET_ALL_ITEMS_ERROR: GET_ALL_CATEGORIES_ERROR, allItemsEnableLoadingAction: allCategoriesEnableLoading });



export const categoryEnableLoading = () => ({ type: CATEGORY_ENABLE_LOADING });

export const createCategory = createFormDataItem({ url: "/api/v1/categories", CREATE_ITEM: CREATE_CATEGORY, CREATE_ITEM_ERROR: CREATE_CATEGORY_ERROR ,itemEnableLoadingAction:categoryEnableLoading});
export const getAllCategories = getAllItems({ url: "/api/v1/categories", GET_ALL_ITEMS: GET_ALL_CATEGORIES, ITEM_ERROR: CATEGORY_ERROR, itemEnableLoading: categoryEnableLoading });

export const createCategory = createFormDataItem({ url: "/api/v1/categories", CREATE_ITEM: CREATE_CATEGORY, ITEM_ERROR: CATEGORY_ERROR, itemEnableLoading: categoryEnableLoading });

15 changes: 4 additions & 11 deletions src/redux/actions/productActions.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { ALL_PRODUCTS_ENABLE_LOADING, CREATE_PRODUCT, CREATE_PRODUCT_ERROR, GET_ALL_PRODUCTS, GET_ALL_PRODUCTS_ERROR, PRODUCT_ENABLE_LOADING } from "../types/productTypes";
import { CREATE_PRODUCT, GET_ALL_PRODUCTS, PRODUCT_ENABLE_LOADING, PRODUCT_ERROR } from "../types/productTypes";
import createFormDataItem from "./utils/createFormDataItem";
import getAllItems from "./utils/getAllItems";




export const allProductsEnableLoading = () => ({ type: ALL_PRODUCTS_ENABLE_LOADING });


export const getAllProducts = getAllItems({ url: "/api/v1/products", GET_ALL_ITEMS: GET_ALL_PRODUCTS, GET_ALL_ITEMS_ERROR: GET_ALL_PRODUCTS_ERROR, allItemsEnableLoadingAction: allProductsEnableLoading });



export const productEnableLoading = () => ({ type: PRODUCT_ENABLE_LOADING });

export const createProduct = createFormDataItem({ url: "/api/v1/products", CREATE_ITEM: CREATE_PRODUCT, CREATE_ITEM_ERROR: CREATE_PRODUCT_ERROR ,itemEnableLoadingAction:productEnableLoading});
export const getAllProducts = getAllItems({ url: "/api/v1/products", GET_ALL_ITEMS: GET_ALL_PRODUCTS, ITEM_ERROR: PRODUCT_ERROR, itemEnableLoading: productEnableLoading });

export const createProduct = createFormDataItem({ url: "/api/v1/products", CREATE_ITEM: CREATE_PRODUCT, ITEM_ERROR: PRODUCT_ERROR, itemEnableLoading: productEnableLoading });

14 changes: 4 additions & 10 deletions src/redux/actions/subCategoryActions.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { CREATE_SUB_CATEGORY, CREATE_SUB_CATEGORY_ERROR, ALL_SUB_CATEGORIES_ENABLE_LOADING, SUB_CATEGORY_ENABLE_LOADING, GET_ALL_SUB_CATEGORIES, GET_ALL_SUB_CATEGORIES_ERROR } from "../types/subCategoryTypes";
import { CREATE_SUB_CATEGORY, GET_ALL_SUB_CATEGORIES, SUB_CATEGORY_ENABLE_LOADING, SUB_CATEGORY_ERROR } from "../types/subCategoryTypes";
import createRawDataItem from "./utils/createRawDataItem";
import getAllItems from "./utils/getAllItems";



export const subCategoryEnableLoading = () => ({ type: SUB_CATEGORY_ENABLE_LOADING });

export const allSubCategoriesEnableLoading = () => ({ type: ALL_SUB_CATEGORIES_ENABLE_LOADING });
export const getAllSubCategories = getAllItems({ url: "/api/v1/subcategories", GET_ALL_ITEMS: GET_ALL_SUB_CATEGORIES, ITEM_ERROR: SUB_CATEGORY_ERROR, itemEnableLoading: subCategoryEnableLoading });

export const createSubCategory = createRawDataItem({ url: "/api/v1/subcategories", CREATE_ITEM: CREATE_SUB_CATEGORY, ITEM_ERROR: SUB_CATEGORY_ERROR, itemEnableLoading: subCategoryEnableLoading });

export const getAllSubCategories = getAllItems({ url: "/api/v1/subcategories", GET_ALL_ITEMS: GET_ALL_SUB_CATEGORIES, GET_ALL_ITEMS_ERROR: GET_ALL_SUB_CATEGORIES_ERROR, allItemsEnableLoadingAction: allSubCategoriesEnableLoading });



export const createSubCategory = createRawDataItem({ url: "/api/v1/subcategories", CREATE_ITEM: CREATE_SUB_CATEGORY, CREATE_ITEM_ERROR: CREATE_SUB_CATEGORY_ERROR });



export const subCategoryEnableLoading = () => ({ type: SUB_CATEGORY_ENABLE_LOADING });
50 changes: 25 additions & 25 deletions src/redux/actions/utils/createFormDataItem.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
import baseURL from "../../../api/baseURL";
import { notify } from "../../../components/utils/ActionMessageContainer";

const createFormDataItem = ({url,CREATE_ITEM,CREATE_ITEM_ERROR,itemEnableLoadingAction})=>(formData)=>async(dispatch)=>{
const controller = new AbortController()
const id = notify({message:"جاري الحفظ 0%",type:"loading",data:{action:()=>controller.abort()}})

controller.signal.onabort = ()=>{
setTimeout(()=>{
notify({dismissId:id})
notify({message:"تم الإلغاء",type:"warning"})
},1000)
}
const createFormDataItem = ({ url, CREATE_ITEM, ITEM_ERROR, itemEnableLoading }) => (formData) => async (dispatch) => {
const controller = new AbortController();
const id = notify({ message: "جاري الحفظ 0%", type: "loading", data: { action: () => controller.abort() } });

controller.signal.onabort = () => {
setTimeout(() => {
notify({ dismissId: id });
notify({ message: "تم الإلغاء", type: "warning" });
}, 1000);
};

try {
const config = {
signal:controller.signal,
onUploadProgress: function(progressEvent) {
const percentCompleted = Math.round((progressEvent.progress*100))
notify({message:`جاري الحفظ ${percentCompleted}%`,type:"loading",id,progress:progressEvent.progress})
signal: controller.signal,
onUploadProgress: function (progressEvent) {
const percentCompleted = Math.round((progressEvent.progress * 100));
notify({ message: `جاري الحفظ ${percentCompleted}%`, type: "loading", id, progress: progressEvent.progress });
}
}
};

dispatch(itemEnableLoadingAction())
const {data} = await baseURL.postForm(url,formData,config)
dispatch(itemEnableLoading());
const { data } = await baseURL.postForm(url, formData, config);

notify({message:"تم الحفظ بنجاح",type:"success",id})
notify({ message: "تم الحفظ بنجاح", type: "success", id });

return dispatch({
type:CREATE_ITEM,
payload:data
})
type: CREATE_ITEM,
payload: data
});

} catch ({ response, message }) {

notify({message:"حدث خطأ أثناء الحفظ",type:"error",id})
notify({ message: "حدث خطأ أثناء الحفظ", type: "error", id });
return dispatch({
type: CREATE_ITEM_ERROR,
type: ITEM_ERROR,
error: `Error: ${response ? response.data?.message : message}`
});
}
}
};

export default createFormDataItem;
22 changes: 11 additions & 11 deletions src/redux/actions/utils/createRawDataItem.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import baseURL from "../../../api/baseURL";
import { notify } from "../../../components/utils/ActionMessageContainer";

const createRawDataItem = ({url,CREATE_ITEM,CREATE_ITEM_ERROR})=>(rawData)=>async(dispatch)=>{
const id = notify({message:"جاري الحفظ",type:"loading"})
const createRawDataItem = ({ url, CREATE_ITEM, ITEM_ERROR }) => (rawData) => async (dispatch) => {
const id = notify({ message: "جاري الحفظ", type: "loading" });

try {
const {data} = await baseURL.post(url,rawData)
notify({message:"تم الحفظ بنجاح",type:"success",id})
const { data } = await baseURL.post(url, rawData);
notify({ message: "تم الحفظ بنجاح", type: "success", id });
return dispatch({
type:CREATE_ITEM,
payload:data
})
type: CREATE_ITEM,
payload: data
});

} catch ({ response, message }) {
notify({message:"حدث خطأ أثناء الحفظ",type:"error",id})
notify({ message: "حدث خطأ أثناء الحفظ", type: "error", id });
return dispatch({
type: CREATE_ITEM_ERROR,
type: ITEM_ERROR,
error: `Error: ${response ? response.data?.message : message}`
});
}
}
};

export default createRawDataItem;
Loading

0 comments on commit 2659c13

Please sign in to comment.