diff --git a/src/components/Input/Input.js b/src/components/Input/Input.js
index e486cbd..ed85418 100644
--- a/src/components/Input/Input.js
+++ b/src/components/Input/Input.js
@@ -1,9 +1,31 @@
+import { useState, useEffect } from "react";
import "./Input.module.scss";
-export function Input({ onChange }) {
+export function Input({ value, onChange }) {
+ const [inputValue, setInputValue] = useState("");
+
+ let emitValue = true;
+
+ useEffect(() => {
+ if (value !== inputValue) {
+ emitValue = false;
+ setInputValue(value);
+ }
+ }, [value]);
+
const updateValue = (v) => {
- onChange(v);
+ if (v === value) return;
+
+ setInputValue(v);
+
+ if (emitValue) {
+ onChange(v);
+ } else {
+ emitValue = true;
+ }
};
- return updateValue(e.target.value)} />;
+ return (
+ updateValue(e.target.value)} />
+ );
}
diff --git a/src/components/Main/Main.js b/src/components/Main/Main.js
index 9946c3a..8315fb9 100644
--- a/src/components/Main/Main.js
+++ b/src/components/Main/Main.js
@@ -2,32 +2,19 @@ import { useState } from "react";
import { Search } from "../Search/Search";
import { Results } from "../Results/Results";
import styles from "./Main.module.scss";
+import SearchProvider from "../../context/SearchContext";
export function Main() {
- const [results, setResults] = useState([]);
-
- const onSearch = async (v) => {
- const res = await search(v);
- setResults(res);
- };
-
return (
-
-
-
-
-
+
);
}
-
-function search(v) {
- return fetch(`http://localhost:4000/search?text=${v}`)
- .then((res) => res.json())
- .catch((err) => {
- console.error(err);
- return [];
- });
-}
diff --git a/src/components/Results/Results.js b/src/components/Results/Results.js
index 723ac25..44f3db0 100644
--- a/src/components/Results/Results.js
+++ b/src/components/Results/Results.js
@@ -1,18 +1,15 @@
import { TranslationBlock } from "../TranslationBlock/TranslationBlock";
import styles from "./Results.module.scss";
+import { useSearch } from "../../context/SearchContext";
+
+export function Results() {
+ const { searchResults } = useSearch();
-export function Results({ results, onSelect }) {
return (
- {(results?.length &&
- results.map((tb, i) => {
- return (
-
- );
+ {(searchResults?.length &&
+ searchResults.map((tb, i) => {
+ return
;
})) ||
No results found
}
);
diff --git a/src/components/Search/Search.js b/src/components/Search/Search.js
index 321f3c1..5fc80a9 100644
--- a/src/components/Search/Search.js
+++ b/src/components/Search/Search.js
@@ -2,24 +2,31 @@ import { useState, useEffect } from "react";
import { Input } from "../Input/Input";
import { SearchOptions } from "../SearchOptions/SearchOptions";
import styles from "./Search.module.scss";
+import { useSearchDispatch, useSearch } from "../../context/SearchContext";
-export function Search({ onSearch }) {
+export function Search() {
const [query, setQuery] = useState("");
const [lookupResults, setLookupResults] = useState([]);
const [selectedIndex, setSelectedIndex] = useState(-1);
- const handler = (e) => {
+ const { searchQuery, searchResults } = useSearch();
+ const dispatch = useSearchDispatch();
+
+ const keyDownHandler = (e) => {
+ if (!["Escape", "Enter", "ArrowDown", "ArrowUp"].includes(e.key)) return;
+
+ e.preventDefault();
+
if (e.key === "Escape") {
- e.preventDefault();
- setLookupResults([]);
+ updateLookupResults([]);
} else if (e.key === "Enter") {
- e.preventDefault();
- onSelect(lookupResults[selectedIndex] || query);
+ dispatch({
+ type: "SET_SEARCH_QUERY",
+ payload: lookupResults[selectedIndex] || query,
+ });
} else if (e.key === "ArrowDown") {
- e.preventDefault();
setSelectedIndex((si) => ((si + 2) % (lookupResults.length + 1)) - 1);
} else if (e.key === "ArrowUp") {
- e.preventDefault();
setSelectedIndex(
(si) =>
((si + lookupResults.length + 1) % (lookupResults.length + 1)) - 1
@@ -28,32 +35,46 @@ export function Search({ onSearch }) {
};
useEffect(() => {
- document.addEventListener("keydown", handler);
-
- return () => document.removeEventListener("keydown", handler);
+ document.addEventListener("keydown", keyDownHandler);
+ return () => document.removeEventListener("keydown", keyDownHandler);
}, [lookupResults, selectedIndex]);
- const onSelect = (v) => {
- setSelectedIndex(-1);
- setLookupResults([]);
- onSearch(v);
- };
+ useEffect(() => {
+ setQuery(searchQuery);
+ searchTranslation(searchQuery);
+ }, [searchQuery]);
+
+ useEffect(() => {
+ updateLookupResults([]);
+ }, [searchResults]);
- const onChange = async (v) => {
+ const onInputUpdate = async (v) => {
setQuery(v);
- setSelectedIndex(-1);
if (!v) {
- setLookupResults([]);
+ updateLookupResults([]);
} else {
const res = await lookup(v);
- setLookupResults(res);
+ updateLookupResults(res);
}
};
+ const searchTranslation = async (v) => {
+ const res = await search(v);
+ dispatch({
+ type: "SET_SEARCH_RESULTS",
+ payload: res,
+ });
+ };
+
+ const updateLookupResults = (newResults) => {
+ setSelectedIndex(-1);
+ setLookupResults(newResults);
+ };
+
return (
-
+
@@ -70,3 +91,12 @@ function lookup(v) {
return [];
});
}
+
+function search(v) {
+ return fetch(`http://localhost:4000/search?text=${v}`)
+ .then((res) => res.json())
+ .catch((err) => {
+ console.error(err);
+ return [];
+ });
+}
diff --git a/src/components/SearchOptions/SearchOptions.js b/src/components/SearchOptions/SearchOptions.js
index 78cecaa..a9ba7b1 100644
--- a/src/components/SearchOptions/SearchOptions.js
+++ b/src/components/SearchOptions/SearchOptions.js
@@ -1,12 +1,24 @@
+import { useSearchDispatch } from "../../context/SearchContext";
import styles from "./SearchOptions.module.scss";
export function SearchOptions({ options, selectedIndex }) {
+ const dispatch = useSearchDispatch();
+
return (
<>
{options?.length > 0 && (
{options.map((option, i) => (
- -
+
-
+ dispatch({
+ type: "SET_SEARCH_QUERY",
+ payload: option,
+ })
+ }
+ >
{option}
))}
diff --git a/src/components/TranslationBlock/TranslationBlock.js b/src/components/TranslationBlock/TranslationBlock.js
index 26653be..a7e726f 100644
--- a/src/components/TranslationBlock/TranslationBlock.js
+++ b/src/components/TranslationBlock/TranslationBlock.js
@@ -1,10 +1,19 @@
-import { Fragment } from "react";
import { TranslationBlockHeader } from "../TranslationBlockHeader/TranslationBlockHeader";
import { TranslationSubject } from "../TranslationSubject/TranslationSubject";
import { TranslationOption } from "../TranslationOption/TranslationOption";
import styles from "./TranslationBlock.module.scss";
+import { useSearchDispatch } from "../../context/SearchContext";
+
+export function TranslationBlock({ translationBlock: tb }) {
+ const dispatch = useSearchDispatch();
+
+ const onSelect = (v) => {
+ dispatch({
+ type: "SET_SEARCH_QUERY",
+ payload: v,
+ });
+ };
-export function TranslationBlock({ translationBlock: tb, onSelect }) {
return (
{
+ switch (action.type) {
+ case "SET_SEARCH_QUERY":
+ return { ...state, searchQuery: action.payload };
+ case "SET_SEARCH_RESULTS":
+ return { ...state, searchResults: action.payload };
+ default:
+ return state;
+ }
+};
+
+const SearchContext = createContext(null);
+const SearchDispatchContext = createContext(null);
+
+export const useSearch = () => useContext(SearchContext);
+export const useSearchDispatch = () => useContext(SearchDispatchContext);
+
+export default function SearchProvider({ children }) {
+ const initialSearchState = {
+ searchQuery: "",
+ searchResults: [],
+ };
+
+ const [search, dispatch] = useReducer(searchReducer, initialSearchState);
+
+ return (
+
+
+ {children}
+
+
+ );
+}