diff --git a/src/App.tsx b/src/App.tsx
index f364926..e3dcd93 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,24 +1,13 @@
-import { FC, useState } from "react";
+import { FC } from "react";
import { PreLoader } from "./components/PreLoader";
import "./index.css";
import MainLayout from "./layouts/MainLayout";
import { AppRouter } from "./router";
import { Router } from "./router/Router";
-import { getData } from "./services/api";
-import { MealSummary } from "./types/meal";
import { useAuth0 } from "./utils/auth0-spa";
export const App: FC = () => {
const { loading } = useAuth0();
- const [searchString, setSearchString] = useState("");
- const [searchResults, setSearchResults] = useState({
- meals: [] as MealSummary[],
- });
- const [_, setMeal] = useState(null);
-
- const getRandomMeal = () => {
- getData("random", setMeal);
- };
return loading ? (
@@ -26,13 +15,8 @@ export const App: FC = () => {
) : (
-
-
+
+
);
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index 1664965..4beee9f 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -8,12 +8,9 @@ import { Logo } from "./Logo";
import { LogOutButton } from "./LogOutButton";
import { RandomButton } from "./RandomButton";
-type Props = {
- openNavClick: React.MouseEventHandler;
- handleClick: () => void;
-};
+type Props = { openNavClick: React.MouseEventHandler };
-export const Navbar: FC = ({ openNavClick, handleClick }) => {
+export const Navbar: FC = ({ openNavClick }) => {
const { isAuthenticated } = useAuth0();
return (
@@ -31,7 +28,6 @@ export const Navbar: FC = ({ openNavClick, handleClick }) => {
)}
void;
color?: string;
};
-export const RandomButton: FC = ({
- url,
- size = "large",
- handleClick,
- color,
-}) => {
+export const RandomButton: FC = ({ url, size = "large", color }) => {
const classString = `waves-effect waves-light btn-${size} ${color}`;
+ const { dispatch } = useMeal();
return (
-
-
+
diff --git a/src/constants.ts b/src/constants.ts
index 1ab5cfb..ec872f8 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,2 +1,3 @@
+export const apiRoot = "https://www.themealdb.com/api/json/v1/1/";
export const buttonURL = "/random";
export const links = ["categories", "contact"];
diff --git a/src/containers/Categories/index.tsx b/src/containers/Categories/index.tsx
index 5719e81..10bc2f6 100644
--- a/src/containers/Categories/index.tsx
+++ b/src/containers/Categories/index.tsx
@@ -7,7 +7,7 @@ export const Categories = () => {
const [categories, setCategories] = useState({ categories: [] });
const getCategories = () => {
- getData("categories", setCategories);
+ getData("categories").then((data) => setCategories(data));
};
useEffect(() => {
diff --git a/src/containers/Category/index.tsx b/src/containers/Category/index.tsx
index f86e781..fd64c84 100644
--- a/src/containers/Category/index.tsx
+++ b/src/containers/Category/index.tsx
@@ -8,8 +8,8 @@ export const Category: FC = () => {
const [meals, setMeals] = useState({ meals: [] });
useEffect(() => {
- const getMeals = () => getData(strCategory, setMeals, "filter");
- getMeals();
+ const getMeals = () => getData(strCategory, "filter");
+ getMeals().then((data) => setMeals(data));
}, [strCategory]);
return !meals.meals ? (
diff --git a/src/containers/Home/index.tsx b/src/containers/Home/index.tsx
index 6953139..f776bf2 100644
--- a/src/containers/Home/index.tsx
+++ b/src/containers/Home/index.tsx
@@ -8,12 +8,7 @@ export const Home: FC = () => (
Chef's Online Cookbook
- {}}
- />
+
diff --git a/src/containers/Meal/index.tsx b/src/containers/Meal/index.tsx
index 853bc12..c7b1a7c 100644
--- a/src/containers/Meal/index.tsx
+++ b/src/containers/Meal/index.tsx
@@ -1,8 +1,8 @@
-import React, { FC, useEffect, useState } from "react";
+import { FC, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
-import { getData } from "../../services/api";
import { useFirebase } from "../../services/Firebase";
-import { MealApi as MealType } from "../../types/meal";
+import { useMeal } from "../../store/meal";
+import { fetchMeal, fetchRandomMeal } from "../../store/meal/async";
import { useAuth0 } from "../../utils/auth0-spa";
import { NotFound } from "../NotFound";
import { MealPage } from "./components/MealPage";
@@ -12,31 +12,18 @@ export const Meal: FC = () => {
// hooks
const { user, isAuthenticated } = useAuth0();
const { id } = useParams<{ id: string }>();
+ const { state, dispatch } = useMeal();
const fb = useFirebase();
// local state
- const [meal, setMeal] = useState({ meals: [] as MealType[] });
const [isFav, setIsFav] = useState();
// variables
- const mealItem = meal?.meals?.[0];
-
- const getMeal = (
- id: string,
- setMeal: React.Dispatch>
- ) => {
- getData(id, setMeal, "lookup");
- };
-
- const getRandomMeal = (
- setMeal: React.Dispatch>
- ) => {
- getData("random", setMeal);
- };
-
+ const mealItem = state.meals?.[0];
// effects
/** Fetch meal from db */
useEffect(() => {
- !id ? getRandomMeal(setMeal) : getMeal(id, setMeal);
- }, [id]);
+ !id ? fetchRandomMeal(dispatch) : fetchMeal(dispatch, id);
+ }, [id, dispatch]);
+
/** Updates fav status in db */
useEffect(() => {
if (isAuthenticated) {
@@ -63,7 +50,7 @@ export const Meal: FC = () => {
const item = buildMealProps(mealItem, isFav!);
const ingredients = buildIngredientList(mealItem);
- return !!meal?.meals ? (
+ return !!state.meals ? (
(
/>
- {}} />
+
diff --git a/src/containers/Search/components/SearchPage.tsx b/src/containers/Search/components/SearchPage.tsx
index d9f4386..3c460f9 100644
--- a/src/containers/Search/components/SearchPage.tsx
+++ b/src/containers/Search/components/SearchPage.tsx
@@ -6,29 +6,26 @@ import { SearchResult } from "./SearchResult";
type Props = {
searchString: string;
- searchResults: { meals: MealSummary[] };
+ searchResults: MealSummary[];
};
-export const SearchPage: FC = ({ searchString, searchResults }) => {
- const { meals } = searchResults;
- return (
-
- {!meals ? (
-
-
- No results to display, instead there is a picture of my breakfast.
-
-

-
- ) : (
-
-
- {meals.map((meal, i) => (
-
- ))}
-
-
- )}
-
- );
-};
+export const SearchPage: FC = ({ searchString, searchResults }) => (
+
+ {!searchResults ? (
+
+
+ No results to display, instead there is a picture of my breakfast.
+
+

+
+ ) : (
+
+
+ {searchResults.map((meal, i) => (
+
+ ))}
+
+
+ )}
+
+);
diff --git a/src/containers/Search/index.tsx b/src/containers/Search/index.tsx
index 181e596..abb94a1 100644
--- a/src/containers/Search/index.tsx
+++ b/src/containers/Search/index.tsx
@@ -1,12 +1,13 @@
import { FC } from "react";
-import { MealSummary } from "../../types/meal";
+import { useMeal } from "../../store/meal";
import { SearchPage } from "./components/SearchPage";
-type Props = {
- searchString: string;
- searchResults: { meals: MealSummary[] };
+export const Search: FC = () => {
+ const { state } = useMeal();
+ return (
+
+ );
};
-
-export const Search: FC = ({ searchString, searchResults }) => (
-
-);
diff --git a/src/index.jsx b/src/index.jsx
index abf04eb..126a756 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -6,6 +6,7 @@ import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "./utils/auth0-spa";
import history from "./utils/history";
import { FirebaseContext } from "./services/Firebase";
+import { AppProvider } from "./store/meal";
const onRedirectCallBack = (appState) => {
history.push(
@@ -24,7 +25,9 @@ ReactDOM.render(
>
{/* todo fix Firebase app*/}
-
+
+
+
,
document.getElementById("root")
diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx
index bb28fdf..5bc05bc 100644
--- a/src/layouts/MainLayout.tsx
+++ b/src/layouts/MainLayout.tsx
@@ -3,23 +3,8 @@ import { Footer } from "../components/Footer";
import { Navbar } from "../components/Navbar";
import { SearchBar } from "../components/SearchBar";
import { SideNav } from "../components/SideNav";
-import { MealSummary } from "../types/meal";
-type Props = {
- getRandomMeal: () => void;
- searchString: string;
- setSearchString: React.Dispatch>;
- setSearchResults: React.Dispatch<
- React.SetStateAction<{ meals: MealSummary[] }>
- >;
-};
-const MainLayout: FC = ({
- getRandomMeal,
- searchString,
- setSearchString,
- setSearchResults,
- children,
-}) => {
+const MainLayout: FC = ({ children }) => {
const [showNav, setShowNav] = useState(false);
const openNavClick: React.MouseEventHandler = (e) => {
@@ -43,18 +28,9 @@ const MainLayout: FC = ({
return (
<>
{children}
diff --git a/src/layouts/PageLayout.tsx b/src/layouts/PageLayout.tsx
index 04e0f1c..9360977 100644
--- a/src/layouts/PageLayout.tsx
+++ b/src/layouts/PageLayout.tsx
@@ -1,8 +1,6 @@
import { FC } from "react";
-type Props = {
- title: string;
-};
+type Props = { title: string };
const PageLayout: FC = ({ title, children }) => (
diff --git a/src/router/AppRouter.tsx b/src/router/AppRouter.tsx
index 9061ea5..260a676 100644
--- a/src/router/AppRouter.tsx
+++ b/src/router/AppRouter.tsx
@@ -3,20 +3,15 @@ import { Redirect, Route, Switch } from "react-router-dom";
import { buttonURL } from "../constants";
import { Categories } from "../containers/Categories";
import { Category } from "../containers/Category";
+import { Contact } from "../containers/Contact";
import { Home } from "../containers/Home";
import { Meal } from "../containers/Meal";
+import { NotFound } from "../containers/NotFound";
import { Profile } from "../containers/Profile";
import { Search } from "../containers/Search";
-import { Contact } from "../containers/Contact";
-import { NotFound } from "../containers/NotFound";
-import { MealSummary } from "../types/meal";
import { PrivateRoute } from "./PrivateRoute";
-type Props = {
- searchString: string;
- searchResults: { meals: MealSummary[] };
-};
-const AppRouter: FC
= ({ searchString, searchResults }) => (
+const AppRouter: FC = () => (
@@ -37,7 +32,7 @@ const AppRouter: FC = ({ searchString, searchResults }) => (
-
+
diff --git a/src/services/api.ts b/src/services/api.ts
index 8bf47ea..b8137da 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -1,20 +1,21 @@
-import React from "react";
+import { apiRoot } from "../constants";
-export const createURI = (keyword: string, option?: string) => {
- const ROOT = "https://www.themealdb.com/api/json/v1/1/";
+type Option = "filter" | "lookup" | "search";
+
+const createURI = (keyword: string, option?: Option) => {
if (!option) {
- return `${ROOT}${keyword}.php`;
+ return `${apiRoot}${keyword}.php`;
}
switch (option) {
case "filter": {
- return `${ROOT}${option}.php?c=${keyword}`;
+ return `${apiRoot}${option}.php?c=${keyword}`;
}
case "lookup": {
- return `${ROOT}${option}.php?i=${keyword}`;
+ return `${apiRoot}${option}.php?i=${keyword}`;
}
case "search": {
- return `${ROOT}${option}.php?s=${keyword}`;
+ return `${apiRoot}${option}.php?s=${keyword}`;
}
default: {
throw Error("Unexpected URI");
@@ -22,15 +23,10 @@ export const createURI = (keyword: string, option?: string) => {
}
};
-export const getData = (
- keyword: string,
- set: React.Dispatch>,
- option?: string
-) => {
+export const getData = (keyword: string, option?: Option) => {
const URI = createURI(keyword, option);
- fetch(URI)
+ return fetch(URI)
.then((response) => response.json())
- .catch((error) => console.warn(error + "url:" + URI))
- .then((data) => set(data));
+ .catch((error) => console.warn(error + "url:" + URI));
};
diff --git a/src/store/meal/async.ts b/src/store/meal/async.ts
new file mode 100644
index 0000000..ab67f91
--- /dev/null
+++ b/src/store/meal/async.ts
@@ -0,0 +1,23 @@
+import { getData } from "../../services/api";
+import { Dispatch } from "./reducer";
+
+export const fetchRandomMeal = async (dispatch: Dispatch) => {
+ const meal = await getData("random");
+ dispatch({ type: "setMeal", payload: meal?.meals });
+};
+
+export const fetchMeal = async (dispatch: Dispatch, id: string) => {
+ const meal = await getData(id, "lookup");
+ dispatch({ type: "setMeal", payload: meal?.meals });
+};
+
+export const fetchSearchResults = async (
+ dispatch: Dispatch,
+ searchString: string
+) => {
+ const meals = await getData(searchString, "search");
+ dispatch({
+ type: "setSearchResults",
+ payload: { search: meals?.meals, searchString },
+ });
+};
diff --git a/src/store/meal/index.tsx b/src/store/meal/index.tsx
new file mode 100644
index 0000000..9e2a068
--- /dev/null
+++ b/src/store/meal/index.tsx
@@ -0,0 +1,35 @@
+//https://kentcdodds.com/blog/how-to-use-react-context-effectively
+
+import { createContext, FC, useContext, useReducer } from "react";
+import { MealApi, MealSummary } from "../../types/meal";
+import { appReducer, Dispatch } from "./reducer";
+
+export type AppState = {
+ meals: MealApi[];
+ search: MealSummary[];
+ searchString: string;
+};
+
+const initState = {
+ meals: [] as MealApi[],
+ search: [] as MealSummary[],
+ searchString: "",
+};
+
+type ContextType = { state: AppState; dispatch: Dispatch } | undefined;
+
+const AppContext = createContext(undefined);
+
+export const useMeal = () => {
+ const context = useContext(AppContext);
+ if (!context) {
+ throw new Error("useMeal must be used within a AppProvider");
+ }
+ return context;
+};
+
+export const AppProvider: FC = ({ children }) => {
+ const [state, dispatch] = useReducer(appReducer, initState);
+ const value = { state, dispatch };
+ return {children};
+};
diff --git a/src/store/meal/reducer.ts b/src/store/meal/reducer.ts
new file mode 100644
index 0000000..84d850c
--- /dev/null
+++ b/src/store/meal/reducer.ts
@@ -0,0 +1,27 @@
+import { MealSummary } from "../../types/meal";
+import { AppState } from "./index";
+
+export const appReducer = (state: AppState, action: Action) => {
+ switch (action.type) {
+ case "setMeal":
+ return { ...state, meals: action.payload };
+ case "setSearchResults":
+ return {
+ ...state,
+ search: action.payload.search,
+ searchString: action.payload.searchString,
+ };
+ case "clearSearchResults":
+ return { ...state, search: [] as MealSummary[] };
+ default: {
+ throw new Error(`Unhandled action type: ${action.type}`);
+ }
+ }
+};
+
+export type Action = {
+ payload?: any;
+ type: "setMeal" | "setSearchResults" | "clearSearchResults";
+};
+
+export type Dispatch = (action: Action) => void;