-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/#183 save user recent searches (last 5) #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
56624de
6a2262d
dfe882c
184f926
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './recent-searches'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export * from "./recent-searches.component" | ||
| export * from "./useRecentSearches.hook" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import Link from "next/link"; | ||
| import { EmbalseSearchModel } from "../../embalse-search.vm"; | ||
|
|
||
| interface Props { | ||
| searches: EmbalseSearchModel[]; | ||
| } | ||
|
|
||
| export const RecentSearches: React.FC<Props> = (props) => { | ||
| const { searches } = props; | ||
|
|
||
| return ( | ||
| <> | ||
| <h3>Búsquedas recientes</h3> | ||
| <ul> | ||
| {searches.map((item) => ( | ||
| <li key={item.slug} className="mt-3"> | ||
| <Link className="link-accessible" href={`/embalse/${item.slug}`}>{item.name} </Link> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { useState, useEffect } from "react"; | ||
| import { EmbalseSearchModel } from "../../embalse-search.vm"; | ||
|
|
||
| export const useRecentSearches = () => { | ||
| const [newSearch, setNewSearch] = useState<EmbalseSearchModel>(null); | ||
| const [recentSearches, setRecentSearches] = useState<EmbalseSearchModel[]>( | ||
| [], | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| const storedSearches = localStorage.getItem("recent-searches"); | ||
| if (storedSearches) { | ||
| const parsedStoredSearches = JSON.parse(storedSearches); | ||
| setRecentSearches(parsedStoredSearches); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move this to a business method as well, and ensure to cover edge cases here, What happens if we get an exception? localStorage may be disabled (e.g. in some browser in icognito mode), what happens if you we null or undefined, what happens if we get an string instead of an array, we should default it to empty array in that case |
||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (newSearch) { | ||
| setRecentSearches((prevSearches) => { | ||
| const filteredRecentSearches = prevSearches.filter( | ||
| (search) => search.slug !== newSearch.slug, | ||
| ); | ||
| return [newSearch, ...filteredRecentSearches].slice(0, 5); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's create a const addNewSearchEntry = (newEntry: string,prevSearches: string[]) : string => {
const filteredRecentSearches = prevSearches.filter(
(search) => search.slug !== newSearch.slug,
);
return [newSearch, ...filteredRecentSearches].slice(0, 5);
}Then use that in this hook, and add unit test support. |
||
| }); | ||
| } | ||
| }, [newSearch]); | ||
|
|
||
| useEffect(() => { | ||
| if (recentSearches.length > 0) { | ||
| const stringfiedRecentSearches = JSON.stringify(recentSearches); | ||
| localStorage.setItem("recent-searches", stringfiedRecentSearches); | ||
| } | ||
| }, [recentSearches]); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmmm... why do we need this method? we are adding recent searches one by one, this could lead to issues if we are adding a new search this could lead to an infinite loop? |
||
|
|
||
| return { | ||
| newSearch, | ||
| setNewSearch, | ||
| recentSearches, | ||
| setRecentSearches, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think you don't need to expose |
||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { EmbalseSearchModel } from "./embalse-search.vm"; | |
| import { getFilteredEmbalses as getFilteredEmbalsesBusiness } from "./embalse-search.business"; | ||
| import { FilteredList } from "./components/filtered-list"; | ||
| import { Input } from "./components/input"; | ||
| import { RecentSearches, useRecentSearches } from "./components"; | ||
|
|
||
| interface Props { | ||
| embalses: Embalse[]; | ||
|
|
@@ -22,6 +23,7 @@ export const EmbalseSearch: React.FC<Props> = (props) => { | |
| >([]); | ||
| const [isNavigating, setIsNavigating] = useState<boolean>(false); | ||
| const [inputValue, setInputValue] = useState<string>(""); | ||
| const { setNewSearch, recentSearches } = useRecentSearches(); | ||
|
|
||
| const getFilteredEmbalses = (inputValue: string): EmbalseSearchModel[] => { | ||
| return getFilteredEmbalsesBusiness(inputValue, embalses); | ||
|
|
@@ -42,12 +44,14 @@ export const EmbalseSearch: React.FC<Props> = (props) => { | |
| onSelectedItemChange: ({ selectedItem }) => { | ||
| if (selectedItem) { | ||
| setIsNavigating(true); | ||
| setNewSearch(selectedItem); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's name this: `addNewEmbalseToLatestSearchCollection`` I think it's more meaninful |
||
| router.push(`/embalse/${selectedItem.slug}`); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| const showNoResults = inputValue.length > 0 && filteredEmbalses.length === 0 && !isNavigating; | ||
| const showNoResults = | ||
| inputValue.length > 0 && filteredEmbalses.length === 0 && !isNavigating; | ||
|
|
||
| return ( | ||
| <div className="relative flex flex-1 flex-col overflow-hidden p-8"> | ||
|
|
@@ -57,15 +61,19 @@ export const EmbalseSearch: React.FC<Props> = (props) => { | |
| ></div> | ||
| <div className="flex grow flex-col items-center justify-center"> | ||
| <section | ||
| className="bg-base-100 absolute flex max-w-10/12 flex-col gap-8 rounded-xl p-8 shadow-lg" | ||
| className="bg-base-100 absolute flex max-w-10/12 flex-col gap-3 rounded-xl p-8 shadow-lg" | ||
| aria-labelledby="search-title" | ||
| > | ||
| <div className="text-center"> | ||
| <h2 id="search-title" className="font-bold"> | ||
| Embalses | ||
| </h2> | ||
| </div> | ||
|
|
||
| <div> | ||
| <p className="text-sm"> | ||
| Encuentra toda la información disponible de los embalses de España | ||
| </p> | ||
| </div> | ||
| <div className="flex flex-col gap-4"> | ||
| <div className="relative" role="search"> | ||
| <Input getInputProps={getInputProps} /> | ||
|
|
@@ -78,13 +86,12 @@ export const EmbalseSearch: React.FC<Props> = (props) => { | |
| /> | ||
| {showNoResults && <NoResult inputValue={inputValue} />} | ||
| </div> | ||
| <div> | ||
| <p className="text-sm"> | ||
| Encuentra toda la información disponible de los embalses de | ||
| España | ||
| </p> | ||
| </div> | ||
| </div> | ||
| {recentSearches.length > 0 && ( | ||
| <div className="mt-5"> | ||
| <RecentSearches searches={recentSearches} /> | ||
| </div> | ||
| )} | ||
| </section> | ||
| </div> | ||
| </div> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could simplify this, something like: