From da287cd6e4336ba8c243811bd0fb72928a05c167 Mon Sep 17 00:00:00 2001 From: Emma Fredriksen Date: Wed, 16 Sep 2026 20:14:31 +0200 Subject: [PATCH 1/3] refactor: move searchbar form to a seperate component --- src/components/Searchbar.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/components/Searchbar.tsx diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx new file mode 100644 index 0000000..7c72a24 --- /dev/null +++ b/src/components/Searchbar.tsx @@ -0,0 +1,29 @@ +import SearchIcon from "../assets/search.svg"; + +//use props to send the values to CharactersPage +type SearchBarProps = { + searchQuery: string; + onSearchChange: (value: string) => void; +}; + +export default function Searchbar({ + searchQuery, + onSearchChange, +}: SearchBarProps) { + return ( +
event.preventDefault()}> + onSearchChange(event.target.value)} + /> + +
+ ); +} From 11f5912d2a2a7b226c912d46de11e21c3694cdac Mon Sep 17 00:00:00 2001 From: Emma Fredriksen Date: Wed, 16 Sep 2026 20:53:09 +0200 Subject: [PATCH 2/3] refactor: move filter logic to seperate file --- src/utils/filterCharacters.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/utils/filterCharacters.ts diff --git a/src/utils/filterCharacters.ts b/src/utils/filterCharacters.ts new file mode 100644 index 0000000..fca1564 --- /dev/null +++ b/src/utils/filterCharacters.ts @@ -0,0 +1,32 @@ +import type { Character } from "../types/Character"; + +export type FilterCategory = "gender" | "species" | "status"; +export type SelectedFilters = Record; + +export function filterCharacters( + // Takes in all characters, the value in the search field and the chosen filters + characters: Character[], + searchQuery: string, + selectedFilters: SelectedFilters, +): Character[] { + return characters.filter((character) => { + //Checks whether each character's name matches with the search and if the character's gender, specie or status is checked off in the filter + const matchesSearch = character.name + .toLowerCase() + .includes(searchQuery.toLowerCase()); + + const matchesGender = + selectedFilters.gender.length === 0 || + selectedFilters.gender.includes(character.gender.toLowerCase()); + + const matchesSpecies = + selectedFilters.species.length === 0 || + selectedFilters.species.includes(character.species.toLowerCase()); + + const matchesStatus = + selectedFilters.status.length === 0 || + selectedFilters.status.includes(character.status.toLowerCase()); + //Returns the result of the checks mentioned above + return matchesSearch && matchesGender && matchesSpecies && matchesStatus; + }); +} From d9d5e5684a40fdeebc9b0f019ac7fee13d014963 Mon Sep 17 00:00:00 2001 From: Emma Fredriksen Date: Wed, 16 Sep 2026 20:53:46 +0200 Subject: [PATCH 3/3] refactor: remove filter logic from character page --- src/components/Searchbar.tsx | 6 +-- src/pages/CharactersPage.tsx | 100 ++++++++++++++--------------------- 2 files changed, 44 insertions(+), 62 deletions(-) diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx index 7c72a24..b123de9 100644 --- a/src/components/Searchbar.tsx +++ b/src/components/Searchbar.tsx @@ -1,6 +1,6 @@ import SearchIcon from "../assets/search.svg"; -//use props to send the values to CharactersPage +//Values recived from CharactersPage as props type SearchBarProps = { searchQuery: string; onSearchChange: (value: string) => void; @@ -21,8 +21,8 @@ export default function Searchbar({ value={searchQuery} onChange={(event) => onSearchChange(event.target.value)} /> - ); diff --git a/src/pages/CharactersPage.tsx b/src/pages/CharactersPage.tsx index fc16ddd..7eb9710 100644 --- a/src/pages/CharactersPage.tsx +++ b/src/pages/CharactersPage.tsx @@ -1,14 +1,19 @@ import "./CharactersPage.css"; import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; + import CharacterCardSmall from "../components/CharacterCardSmall"; import CharacterDetailCard from "../components/CharacterDetailCard"; -import SearchIcon from "../assets/search.svg"; -import { useQuery } from '@tanstack/react-query'; +import Searchbar from "../components/Searchbar"; import type { Character } from "../types/Character"; -type FilterCategory = "gender" | "species" | "status"; +import { + filterCharacters, + type FilterCategory, + type SelectedFilters, +} from "../utils/filterCharacters"; const filters: { title: string; key: FilterCategory; options: string[] }[] = [ { title: "Gender", key: "gender", options: ["Female", "Male", "Unknown"] }, @@ -18,37 +23,41 @@ const filters: { title: string; key: FilterCategory; options: string[] }[] = [ // AI was used to create the fetchCharacters function async function fetchCharacters(): Promise { - const response = await fetch("https://rickandmortyapi.com/api/character"); + const response = await fetch("https://rickandmortyapi.com/api/character"); - if (!response.ok) { - throw new Error(`Could not fetch characters: ${response.status}`); - } + if (!response.ok) { + throw new Error(`Could not fetch characters: ${response.status}`); + } - const data = await response.json(); - return data.results; + const data = await response.json(); + return data.results; } export default function CharactersPage() { - const {data: characters = []} = useQuery({queryKey: ['characters'], queryFn: fetchCharacters}); + const { data: characters = [] } = useQuery({ + queryKey: ["characters"], + queryFn: fetchCharacters, + }); const [selectedCharacter, setSelectedCharacter] = useState( null, ); - const [searchQuery, setsearchQuery] = useState(""); - const [selectedFilters, setSelectedFilters] = useState< - Record - >(() => { - const storedFilters = sessionStorage.getItem("selectedFilters"); - - if (storedFilters) { - return JSON.parse(storedFilters); - } - - return { - gender: [], - species: [], - status: [], - }; - }); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedFilters, setSelectedFilters] = useState( + () => { + const storedFilters = sessionStorage.getItem("selectedFilters"); + + if (storedFilters) { + return JSON.parse(storedFilters); + } + + return { + gender: [], + species: [], + status: [], + }; + }, + ); + const [favoriteCharacterIds, setFavoriteCharacterIds] = useState( () => { const storedFavorites = localStorage.getItem("favoriteCharacterIds"); @@ -72,22 +81,11 @@ export default function CharactersPage() { ); }, [favoriteCharacterIds]); - const filteredCharacters = characters.filter((character) => { - const matchesSearch = character.name - .toLowerCase() - .includes(searchQuery.toLowerCase()); - const matchesFilters = filters.every((filter) => { - const selectedOptions = selectedFilters[filter.key]; - - if (selectedOptions.length === 0) { - return true; - } - - return selectedOptions.includes(character[filter.key].toLowerCase()); - }); - - return matchesSearch && matchesFilters; - }); + const filteredCharacters = filterCharacters( + characters, + searchQuery, + selectedFilters, + ); function handleFilterChange( category: FilterCategory, @@ -145,23 +143,7 @@ export default function CharactersPage() { return (
-
event.preventDefault()} - > - setsearchQuery(event.target.value)} - /> - -
+
{filteredCharacters.map((character) => (