Overview
The Courses page provides comprehensive search and filtering capabilities, allowing students to find courses by title, category, and price. The page also includes pagination for easy navigation through large course catalogs.
Location: src/views/Courses/Courses.jsx
Course Data State
The component manages two data arrays for flexible filtering:
const [dataCourses, setDataCourses] = useState({
data: [],
filteredData: [],
});
Location: src/views/Courses/Courses.jsx:12-15
Complete list of all available courses loaded from localStorage
Subset of courses matching current filter criteria. When empty, displays full course list.
Search by Title
Real-time search functionality filters courses as users type:
const [dataTitle, setDataTitle] = useState("");
const dataFilteredByTitle = async (event) => {
const newValue = event.target.value;
setDataTitle(newValue);
const url = `/courses?title=${newValue}`;
if (newValue) {
const { data } = await axios.get(url);
setDataCourses((prevState) => ({
...prevState,
filteredData: data,
}));
}
};
Location: src/views/Courses/Courses.jsx:18,104-117
<div className={Styles.SearchBarContainer}>
<input
type="text"
value={dataTitle}
name="title"
onChange={dataFilteredByTitle}
/>
<svg>
{/* Search icon */}
</svg>
</div>
Location: src/views/Courses/Courses.jsx:191-208
Auto-reset on Clear
When search is cleared, filters automatically reset:
useEffect(() => {
if (dataTitle === "") {
handleReset();
}
}, [dataTitle]);
Location: src/views/Courses/Courses.jsx:180-184
Category Filter
Users can filter courses by category using a dropdown:
const [categoriesData, setCategoriesData] = useState([]);
const dataFiltered = async (e) => {
const categoryFilter = e.target.value;
const url = `/courses?category=${categoryFilter}`;
if (categoryFilter !== "categories") {
if (categoryFilter) {
const { data } = await axios.get(url);
setDataCourses((prevState) => ({
...prevState,
filteredData: data,
}));
}
} else {
handleReset();
return;
}
};
Location: src/views/Courses/Courses.jsx:17,86-102
Category Dropdown
<select
className={Styles.filterButt}
onChange={dataFiltered}
name="category"
id="category"
>
<option name="categories" value="categories">
Categorías
</option>
{categoriesData?.map((category, index) => (
<option key={index} value={category.name}>
{category.name}
</option>
))}
</select>
Location: src/views/Courses/Courses.jsx:210-223
Loading Categories
Categories are loaded from localStorage on component mount:
useEffect(() => {
const storedData = localStorage.getItem("coursesData");
const parsedData = storedData ? JSON.parse(storedData) : [];
setDataCourses((prevState) => ({
...prevState,
data: parsedData.filter((element) => element.enabled !== false),
}));
setCategoriesData(JSON.parse(localStorage.getItem("categoriesData")));
const session = JSON.parse(localStorage.getItem("userOnSession"));
if (session?.email !== "") {
updateContextUser(session);
}
}, []);
Location: src/views/Courses/Courses.jsx:71-84
Price Sorting
Courses can be sorted by price in ascending or descending order:
const sortByPrice = (e) => {
const sortedData = [...dataCourses.data];
const sortedFilteredData = dataCourses.filteredData.length
? [...dataCourses.filteredData]
: [];
const selectedOpt = e.target.value;
if (selectedOpt === "DESC") {
sortedData.sort((a, b) => b.price - a.price);
sortedFilteredData.sort((a, b) => b.price - a.price);
} else if (selectedOpt === "ASC") {
sortedData.sort((a, b) => a.price - b.price);
sortedFilteredData.sort((a, b) => a.price - b.price);
}
setDataCourses((prevState) => ({
...prevState,
data: sortedData,
filteredData: sortedFilteredData,
}));
};
Location: src/views/Courses/Courses.jsx:119-138
Sort Options
Menor a mayor (Low to High) - Sorts courses from cheapest to most expensive
Mayor a menor (High to Low) - Sorts courses from most expensive to cheapest
Default sorting (no price sort applied)
Price Sort Dropdown
<select
className={Styles.filterButt}
onChange={sortByPrice}
name="order"
id="order"
>
<option name="Default" value="Default">
Ordenar por precio
</option>
<option value="ASC">Menor a mayor</option>
<option value="DESC">Mayor a menor</option>
</select>
Location: src/views/Courses/Courses.jsx:238-248
Reset Filters
Users can clear all filters and return to the full course list:
const handleReset = () => {
const selectElement = document.getElementById("category");
const optionToSelect = selectElement.querySelector(
'option[name="categories"]'
);
if (optionToSelect) {
optionToSelect.selected = true;
}
const selectElement2 = document.getElementById("order");
const optionToSelect2 = selectElement2.querySelector(
'option[name="Default"]'
);
if (optionToSelect2) {
optionToSelect2.selected = true;
}
setDataCourses(() => ({
data: JSON.parse(localStorage.getItem("coursesData")),
filteredData: [],
}));
};
Location: src/views/Courses/Courses.jsx:148-178
<div className={Styles.reset}>
<Button
className="reset-button"
onClick={handleReset}
text={"Restablecer"}
/>
</div>
Location: src/views/Courses/Courses.jsx:250-256
The course list implements pagination for better performance and UX:
const ITEMS_PER_PAGE = 10;
const MAX_PAGES_DISPLAYED = 5;
const [currentPage, setCurrentPage] = useState(1);
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
const displayCourses =
dataCourses.filteredData.length > 0
? dataCourses?.filteredData
: dataCourses?.data;
const totalPages =
displayCourses && displayCourses.length > 0
? Math.ceil(displayCourses.length / ITEMS_PER_PAGE)
: 1;
const currentItems = displayCourses
? displayCourses.slice(startIndex, endIndex)
: [];
Location: src/views/Courses/Courses.jsx:8-35
Number of courses displayed per page
Maximum number of page buttons shown in pagination controls
Navigation Functions
const handleNext = () => {
const totalElements = displayCourses.length;
const nextPage = currentPage + 1;
const totalPages = Math.ceil(totalElements / ITEMS_PER_PAGE);
if (nextPage <= totalPages) {
setCurrentPage(nextPage);
window.scrollTo({ top: 0 });
}
};
const handlePrev = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
window.scrollTo({ top: 0 });
}
};
Location: src/views/Courses/Courses.jsx:54-70
<div className={Styles.paginationContainer}>
<button
className={Styles.paginationBotton}
onClick={handlePrev}
disabled={currentPage === 1}
>
Prev
</button>
{Array.from({ length: endPage - startPage + 1 }, (_, index) => (
<button
key={startPage + index}
onClick={() => setCurrentPage(startPage + index)}
className={
currentPage === startPage + index
? Styles.currentPage
: Styles.buttonPage
}
>
{startPage + index}
</button>
))}
<button
className={Styles.paginationBotton}
onClick={handleNext}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
Location: src/views/Courses/Courses.jsx:274-299
Page Range Calculation
The pagination displays a sliding window of page numbers:
let startPage = currentPage - Math.floor(MAX_PAGES_DISPLAYED / 2);
let endPage = currentPage + Math.floor(MAX_PAGES_DISPLAYED / 2);
if (startPage < 1) {
startPage = 1;
endPage = Math.min(MAX_PAGES_DISPLAYED, totalPages);
}
if (endPage > totalPages) {
endPage = totalPages;
startPage = Math.max(1, totalPages - MAX_PAGES_DISPLAYED + 1);
}
Location: src/views/Courses/Courses.jsx:41-52
Course Display
Courses are filtered to show only enabled courses:
const filteredCourses = currentItems.filter(
(element) => element.enabled === true
);
Location: src/views/Courses/Courses.jsx:37-39
Rendering Courses
<div className={Styles.courses}>
{dataCourses.data && dataCourses.data.length !== 0 ? (
dataCourses.filteredData === false ? (
<div>No hay coincidencias</div>
) : (
filteredCourses.map((course, index) => (
<Card key={index} course={course} />
))
)
) : (
<div>No hay cursos disponibles</div>
)}
</div>
Location: src/views/Courses/Courses.jsx:260-273
Filter Workflow
Initial Load
Courses and categories load from localStorage, displaying all enabled courses
Apply Filter
User selects category or enters search term, triggering API request with filter parameters
Update Filtered Data
API response populates filteredData array, which takes precedence over full data array
Apply Sorting
User can sort filtered results by price (ASC/DESC) without re-fetching data
Pagination Update
Pagination recalculates based on filtered results length
Reset Option
User clicks “Restablecer” to clear filters and return to full course list
API Endpoints
The filters use the following API patterns:
Filter by Title
GET /courses?title={searchTerm}
Returns courses matching the search term in their title.
Filter by Category
GET /courses?category={categoryName}
Returns courses belonging to the specified category.
Data Persistence
Course and category data are cached in localStorage:
Full list of all courses, loaded on app initialization
List of all available categories for the filter dropdown
Current user session data, used to check course ownership
Filter Combination
Filters work together:
- Search or Category: Only one can be active at a time (selecting a category clears search, and vice versa)
- Price Sort: Applied on top of filtered results
- Enabled Filter: Always active - only shows courses with
enabled: true
- Pagination: Works with both filtered and unfiltered data
Empty States
Shown when coursesData is empty or not loaded
Shown when filters return zero results