Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

fix: resolve mobile keyboard fluctuation during search #9248

Merged
merged 16 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 94 additions & 71 deletions pages/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
searchTagNameInInput,
} from "@services/utils/search/tags";
import { PROJECT_NAME } from "@constants/index";
import Button from "@components/Button";

async function fetchUsersByKeyword(keyword) {
const res = await fetch(
Expand Down Expand Up @@ -72,16 +73,16 @@ export default function Search({
data: { tags, recentlyUpdatedUsers, filteredUsers },
BASE_URL,
}) {
const router = useRouter();
const { username, keyword, userSearchParam } = router.query;
const { replace, query, pathname } = useRouter();
const { username, keyword, userSearchParam } = query;
const [notFound, setNotFound] = useState();
const [users, setUsers] = useState(keyword ? filteredUsers : recentlyUpdatedUsers);
const [inputValue, setInputValue] = useState(
username || keyword || userSearchParam || "",
const [users, setUsers] = useState(
keyword ? filteredUsers : recentlyUpdatedUsers,
);
const [currentPage, setCurrentPage] = useState(1);

const searchInputRef = useRef(null);
const searchTerm = username || keyword || userSearchParam;

useEffect(() => {
if (username) {
Expand All @@ -95,29 +96,31 @@ export default function Search({
}, []);

useEffect(() => {
if (!inputValue) {
//Setting the users as null when the input field is empty
setUsers(recentlyUpdatedUsers);
//Removing the not found field when the input field is empty
setNotFound();
router.replace(
const onKeyDownHandler = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
e.preventDefault();
searchInputRef.current?.focus();
}
};
document.addEventListener("keydown", onKeyDownHandler);

return () => {
document.removeEventListener("keydown", onKeyDownHandler);
};
}, []);

useEffect(() => {
if (!searchTerm) {
replace(
{
pathname: "/search",
pathname,
},
undefined,
{ shallow: true },
);
return;
}

// checks if there is no keyword between 2 commas and removes the second comma and also checks if the input starts with comma and removes it.
setInputValue(inputValue.replace(/,(\s*),/g, ",").replace(/^,/, ""));

// If the inputValue has not changed and is the same as the keyword passed from the server
if (keyword && inputValue === keyword) {
return;
}

async function fetchUsers(value) {
try {
const res = await fetch(
Expand All @@ -139,55 +142,65 @@ export default function Search({
}
}

const timer = setTimeout(() => {
router.replace(
{
pathname: "/search",
query: { userSearchParam: inputValue },
},
undefined,
{ shallow: true },
);
fetchUsers(inputValue);
}, 500);

return () => clearTimeout(timer);
}, [inputValue]);
if (searchTerm) {
fetchUsers(searchTerm);
}
}, [searchTerm]);

useEffect(() => {
const onKeyDownHandler = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
e.preventDefault();
searchInputRef.current?.focus();
}
};
document.addEventListener("keydown", onKeyDownHandler);
const handleSearchSubmit = async (e) => {
e.preventDefault();

return () => {
document.removeEventListener("keydown", onKeyDownHandler);
};
}, []);
const params = new URLSearchParams({ query: searchTerm });

const search = (keyword) => {
const cleanedInput = cleanSearchInput(inputValue);
function removeComma(value) {
return value.replace(/,(\s*),/g, ",").replace(/^,/, "");
}
const searchInputVal = removeComma(
cleanSearchInput(searchInputRef.current.value),
);

if (!cleanedInput.length) {
return setInputValue(keyword);
if (!searchInputVal) {
params.delete("query");
} else {
params.set("query", searchInputVal);
}

const items = cleanedInput.split(", ");
replace(
{
pathname,
query: { userSearchParam: params.get("query") },
},
undefined,
{ shallow: true },
);
};

if (cleanedInput.length) {
if (searchTagNameInInput(inputValue, keyword)) {
return setInputValue(
items.filter((item) => item.trim() !== keyword).join(", "),
);
}
const handleSearchTag = (tagName) => {
const params = new URLSearchParams({ query: searchTerm });
if (!userSearchParam) {
params.set("query", tagName);
}

return setInputValue([...items, keyword].join(", "));
if (userSearchParam) {
if (searchTagNameInInput(userSearchParam, tagName)) {
const terms = userSearchParam.split(",");
const filteredTerms = terms.filter((item) => item.trim() !== tagName);
params.set("query", filteredTerms);
} else {
params.append("query", tagName);
}
}

setInputValue(keyword);
replace(
{
pathname,
query: {
userSearchParam: params.getAll("query").join(", "),
},
},
undefined,
{ shallow: true },
);
};

const usersPerPage = 21;
Expand Down Expand Up @@ -218,8 +231,10 @@ export default function Search({
key={tag.name}
name={tag.name}
total={tag.total}
selected={searchTagNameInInput(inputValue, tag.name)}
onClick={() => search(tag.name)}
selected={
searchTerm && searchTagNameInInput(searchTerm, tag.name)
}
onClick={() => handleSearchTag(tag.name)}
/>
))}
</div>
Expand All @@ -230,16 +245,24 @@ export default function Search({
className="w-full"
badgeClassName={"translate-x-2/4 -translate-y-1/2"}
>
<Input
ref={searchInputRef}
placeholder="Search user by name or tags; eg: open source, reactjs or places; eg: London, New York"
name="keyword"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<form onSubmit={handleSearchSubmit} className="w-full">
<Input
ref={searchInputRef}
placeholder="Search user by name or tags; eg: open source, reactjs or places; eg: London, New York"
name="keyword"
defaultValue={searchTerm}
/>
<Button type="submit" className="mb-4">
Search
</Button>
</form>
badrivlog marked this conversation as resolved.
Show resolved Hide resolved
</Badge>

{!inputValue && <h2 className="mt-10 mb-4 text-2xl font-bold">Recently updated profiles</h2>}
{!searchTerm && (
<h2 className="mt-10 mb-4 text-2xl font-bold">
Recently updated profiles
</h2>
)}

{notFound && <Alert type="error" message={notFound} />}
<ul
Expand All @@ -249,14 +272,14 @@ export default function Search({
{users.length < usersPerPage &&
users.map((user) => (
<li key={user.username}>
<UserHorizontal profile={user} input={inputValue} />
<UserHorizontal profile={user} input={searchTerm} />
</li>
))}

{users.length > usersPerPage &&
visibleUsers.map((user) => (
<li key={user.username}>
<UserHorizontal profile={user} input={inputValue} />
<UserHorizontal profile={user} input={searchTerm} />
</li>
))}
</ul>
Expand Down
6 changes: 6 additions & 0 deletions tests/search.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ test("Search works correctly", async ({ page }) => {
// 3. type in search and check that user with the name exist and check a name doesn't exist
const input = page.locator("[name='keyword']");
await input.type("_test-profile-user-1");
await page.locator("[type='submit']").click();

await expect(page.locator("main li")).toHaveCount(1);
});
Expand All @@ -40,6 +41,7 @@ test("Search page has random results when no search term used", async ({

const input = page.locator("[name='keyword']");
await input.fill("");
await page.locator("[type='submit']").click();

await expect(page.locator("main li")).toHaveCount(defaultUsers);
});
Expand All @@ -51,6 +53,7 @@ test("Search page shows random results after typing 1 characters", async ({

const input = page.locator("[name='keyword']");
await input.type("e");
await page.locator("[type='submit']").click();

await expect(page.locator("main li")).toHaveCount(defaultUsers);
});
Expand All @@ -62,6 +65,7 @@ test("Search page shows results after typing 3 characters", async ({

const input = page.locator("[name='keyword']");
await input.type("aka");
await page.locator("[type='submit']").click();

await expect(page.locator("main li")).toContainText(["aka"]);
});
Expand All @@ -73,6 +77,7 @@ test("Search term persistence after navigating back", async ({ page }) => {
const searchTerm = "_test-profile-user-1";
const searchName = "Test User Name 1";
await input.fill(searchTerm);
await page.locator("[type='submit']").click();

// 2. Navigate to profile
await expect(page).toHaveURL(`/search?userSearchParam=${searchTerm}`);
Expand Down Expand Up @@ -107,6 +112,7 @@ test("find the profile after providing concise name", async ({ page }) => {
// 3. find the input field and type the whole name
const input = page.locator("[name='keyword']");
await input.fill(searchTerm);
await page.locator("[type='submit']").click();

// 4. select and click on the profile by matching name string
const profileHeader = page.locator(`h2:has-text('${searchTerm}')`);
Expand Down