mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-12 13:26:45 +00:00
async actions
This commit is contained in:
parent
e4d6c31128
commit
c19bb6ac32
10 changed files with 90 additions and 105 deletions
15
src/App.tsx
15
src/App.tsx
|
|
@ -1,18 +1,13 @@
|
||||||
import { FC, useState } from "react";
|
import { FC } from "react";
|
||||||
import { PreLoader } from "./components/PreLoader";
|
import { PreLoader } from "./components/PreLoader";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import MainLayout from "./layouts/MainLayout";
|
import MainLayout from "./layouts/MainLayout";
|
||||||
import { AppRouter } from "./router";
|
import { AppRouter } from "./router";
|
||||||
import { Router } from "./router/Router";
|
import { Router } from "./router/Router";
|
||||||
import { MealSummary } from "./types/meal";
|
|
||||||
import { useAuth0 } from "./utils/auth0-spa";
|
import { useAuth0 } from "./utils/auth0-spa";
|
||||||
|
|
||||||
export const App: FC = () => {
|
export const App: FC = () => {
|
||||||
const { loading } = useAuth0();
|
const { loading } = useAuth0();
|
||||||
const [searchString, setSearchString] = useState("");
|
|
||||||
const [searchResults, setSearchResults] = useState({
|
|
||||||
meals: [] as MealSummary[],
|
|
||||||
});
|
|
||||||
|
|
||||||
return loading ? (
|
return loading ? (
|
||||||
<div className="container center-align valign-wrapper">
|
<div className="container center-align valign-wrapper">
|
||||||
|
|
@ -20,12 +15,8 @@ export const App: FC = () => {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Router>
|
<Router>
|
||||||
<MainLayout
|
<MainLayout>
|
||||||
searchString={searchString}
|
<AppRouter />
|
||||||
setSearchResults={setSearchResults}
|
|
||||||
setSearchString={setSearchString}
|
|
||||||
>
|
|
||||||
<AppRouter searchString={searchString} searchResults={searchResults} />
|
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,20 @@
|
||||||
import React, { ChangeEvent, FC } from "react";
|
import React, { ChangeEvent, FC, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getData } from "../services/api";
|
import { useMeal } from "../store/meal";
|
||||||
import { MealSummary } from "../types/meal";
|
import { fetchSearchResults } from "../store/meal/async";
|
||||||
|
|
||||||
type Props = {
|
export const SearchBar: FC = () => {
|
||||||
searchString: string;
|
const { dispatch } = useMeal();
|
||||||
setSearchString: React.Dispatch<React.SetStateAction<string>>;
|
const [searchString, setSearchString] = useState("");
|
||||||
setSearchResults: React.Dispatch<
|
|
||||||
React.SetStateAction<{ meals: MealSummary[] }>
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SearchBar: FC<Props> = ({
|
|
||||||
searchString,
|
|
||||||
setSearchString,
|
|
||||||
setSearchResults,
|
|
||||||
}) => {
|
|
||||||
const getSearchResults: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
const getSearchResults: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
searchString === ""
|
searchString === ""
|
||||||
? e.preventDefault()
|
? e.preventDefault()
|
||||||
: getData(searchString, setSearchResults, "search");
|
: fetchSearchResults(dispatch, searchString);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSearchBar = () => {
|
const clearSearchBar = () => {
|
||||||
setSearchString("");
|
setSearchString("");
|
||||||
setSearchResults({ meals: [] });
|
dispatch({ type: "clearSearchResults" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
|
|
||||||
|
|
@ -6,29 +6,26 @@ import { SearchResult } from "./SearchResult";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
searchString: string;
|
searchString: string;
|
||||||
searchResults: { meals: MealSummary[] };
|
searchResults: MealSummary[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SearchPage: FC<Props> = ({ searchString, searchResults }) => {
|
export const SearchPage: FC<Props> = ({ searchString, searchResults }) => (
|
||||||
const { meals } = searchResults;
|
<PageLayout title={`Results for: ${searchString}`}>
|
||||||
return (
|
{!searchResults ? (
|
||||||
<PageLayout title={`Results for: ${searchString}`}>
|
<div className="center-align">
|
||||||
{!meals ? (
|
<p>
|
||||||
<div className="center-align">
|
No results to display, instead there is a picture of my breakfast.
|
||||||
<p>
|
</p>
|
||||||
No results to display, instead there is a picture of my breakfast.
|
<img src={BreakfastImage} alt="Nothing here!" width="70%" />
|
||||||
</p>
|
</div>
|
||||||
<img src={BreakfastImage} alt="Nothing here!" width="70%" />
|
) : (
|
||||||
</div>
|
<div className="row">
|
||||||
) : (
|
<ul>
|
||||||
<div className="row">
|
{searchResults.map((meal, i) => (
|
||||||
<ul>
|
<SearchResult key={i} meal={meal} />
|
||||||
{meals.map((meal, i) => (
|
))}
|
||||||
<SearchResult key={i} meal={meal} />
|
</ul>
|
||||||
))}
|
</div>
|
||||||
</ul>
|
)}
|
||||||
</div>
|
</PageLayout>
|
||||||
)}
|
);
|
||||||
</PageLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { MealSummary } from "../../types/meal";
|
import { useMeal } from "../../store/meal";
|
||||||
import { SearchPage } from "./components/SearchPage";
|
import { SearchPage } from "./components/SearchPage";
|
||||||
|
|
||||||
type Props = {
|
export const Search: FC = () => {
|
||||||
searchString: string;
|
const { state } = useMeal();
|
||||||
searchResults: { meals: MealSummary[] };
|
return (
|
||||||
|
<SearchPage
|
||||||
|
searchString={state.searchString}
|
||||||
|
searchResults={state.search}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Search: FC<Props> = ({ searchString, searchResults }) => (
|
|
||||||
<SearchPage searchString={searchString} searchResults={searchResults} />
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,8 @@ import { Footer } from "../components/Footer";
|
||||||
import { Navbar } from "../components/Navbar";
|
import { Navbar } from "../components/Navbar";
|
||||||
import { SearchBar } from "../components/SearchBar";
|
import { SearchBar } from "../components/SearchBar";
|
||||||
import { SideNav } from "../components/SideNav";
|
import { SideNav } from "../components/SideNav";
|
||||||
import { MealSummary } from "../types/meal";
|
|
||||||
|
|
||||||
type Props = {
|
const MainLayout: FC = ({ children }) => {
|
||||||
searchString: string;
|
|
||||||
setSearchString: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setSearchResults: React.Dispatch<
|
|
||||||
React.SetStateAction<{ meals: MealSummary[] }>
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
const MainLayout: FC<Props> = ({
|
|
||||||
searchString,
|
|
||||||
setSearchString,
|
|
||||||
setSearchResults,
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [showNav, setShowNav] = useState(false);
|
const [showNav, setShowNav] = useState(false);
|
||||||
|
|
||||||
const openNavClick: React.MouseEventHandler = (e) => {
|
const openNavClick: React.MouseEventHandler = (e) => {
|
||||||
|
|
@ -42,12 +29,7 @@ const MainLayout: FC<Props> = ({
|
||||||
<>
|
<>
|
||||||
<header>
|
<header>
|
||||||
<Navbar openNavClick={openNavClick} />
|
<Navbar openNavClick={openNavClick} />
|
||||||
|
<SearchBar />
|
||||||
<SearchBar
|
|
||||||
searchString={searchString}
|
|
||||||
setSearchString={setSearchString}
|
|
||||||
setSearchResults={setSearchResults}
|
|
||||||
/>
|
|
||||||
<SideNav showNav={showNav} closeNavClick={closeNavClick} />
|
<SideNav showNav={showNav} closeNavClick={closeNavClick} />
|
||||||
</header>
|
</header>
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = { title: string };
|
||||||
title: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PageLayout: FC<Props> = ({ title, children }) => (
|
const PageLayout: FC<Props> = ({ title, children }) => (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,15 @@ import { Redirect, Route, Switch } from "react-router-dom";
|
||||||
import { buttonURL } from "../constants";
|
import { buttonURL } from "../constants";
|
||||||
import { Categories } from "../containers/Categories";
|
import { Categories } from "../containers/Categories";
|
||||||
import { Category } from "../containers/Category";
|
import { Category } from "../containers/Category";
|
||||||
|
import { Contact } from "../containers/Contact";
|
||||||
import { Home } from "../containers/Home";
|
import { Home } from "../containers/Home";
|
||||||
import { Meal } from "../containers/Meal";
|
import { Meal } from "../containers/Meal";
|
||||||
|
import { NotFound } from "../containers/NotFound";
|
||||||
import { Profile } from "../containers/Profile";
|
import { Profile } from "../containers/Profile";
|
||||||
import { Search } from "../containers/Search";
|
import { Search } from "../containers/Search";
|
||||||
import { Contact } from "../containers/Contact";
|
|
||||||
import { NotFound } from "../containers/NotFound";
|
|
||||||
import { MealSummary } from "../types/meal";
|
|
||||||
import { PrivateRoute } from "./PrivateRoute";
|
import { PrivateRoute } from "./PrivateRoute";
|
||||||
|
|
||||||
type Props = {
|
const AppRouter: FC = () => (
|
||||||
searchString: string;
|
|
||||||
searchResults: { meals: MealSummary[] };
|
|
||||||
};
|
|
||||||
const AppRouter: FC<Props> = ({ searchString, searchResults }) => (
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/">
|
<Route exact path="/">
|
||||||
<Home />
|
<Home />
|
||||||
|
|
@ -37,7 +32,7 @@ const AppRouter: FC<Props> = ({ searchString, searchResults }) => (
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path="/search">
|
<Route exact path="/search">
|
||||||
<Search searchString={searchString} searchResults={searchResults} />
|
<Search />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/contact">
|
<Route path="/contact">
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,20 @@ export const fetchMeal = async (dispatch: Dispatch, id: string) => {
|
||||||
|
|
||||||
dispatch({ type: "setMeal", payload: meal.meals });
|
dispatch({ type: "setMeal", payload: meal.meals });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchSearchResults = async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
searchString: string
|
||||||
|
) => {
|
||||||
|
//TODO: refactor to use Meal client
|
||||||
|
const URI = createURI(searchString, "search");
|
||||||
|
|
||||||
|
const meals = await fetch(URI)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.catch((error) => console.warn(error + "url:" + URI));
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: "setSearchResults",
|
||||||
|
payload: { search: meals.meals, searchString },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
//https://kentcdodds.com/blog/how-to-use-react-context-effectively
|
//https://kentcdodds.com/blog/how-to-use-react-context-effectively
|
||||||
|
|
||||||
import { createContext, FC, useContext, useReducer } from "react";
|
import { createContext, FC, useContext, useReducer } from "react";
|
||||||
import { MealApi } from "../../types/meal";
|
import { MealApi, MealSummary } from "../../types/meal";
|
||||||
import { appReducer, Dispatch } from "./reducer";
|
import { appReducer, Dispatch } from "./reducer";
|
||||||
|
|
||||||
export type AppState = { meals: MealApi[] };
|
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;
|
type ContextType = { state: AppState; dispatch: Dispatch } | undefined;
|
||||||
|
|
||||||
const AppContext = createContext<ContextType>(undefined);
|
const AppContext = createContext<ContextType>(undefined);
|
||||||
|
|
@ -18,7 +29,7 @@ export const useMeal = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppProvider: FC = ({ children }) => {
|
export const AppProvider: FC = ({ children }) => {
|
||||||
const [state, dispatch] = useReducer(appReducer, { meals: [] as MealApi[] });
|
const [state, dispatch] = useReducer(appReducer, initState);
|
||||||
const value = { state, dispatch };
|
const value = { state, dispatch };
|
||||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ import { AppState } from "./index";
|
||||||
export const appReducer = (state: AppState, action: Action) => {
|
export const appReducer = (state: AppState, action: Action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "setMeal":
|
case "setMeal":
|
||||||
return { meals: action.payload };
|
return { ...state, meals: action.payload };
|
||||||
case "fetchMeal":
|
case "setSearchResults":
|
||||||
return { meals: state.meals };
|
return {
|
||||||
case "fetchRandomMeal":
|
...state,
|
||||||
return { meals: state.meals };
|
search: action.payload.search,
|
||||||
case "toggleFav":
|
searchString: action.payload.searchString,
|
||||||
return { meals: state.meals };
|
};
|
||||||
|
case "clearSearchResults":
|
||||||
|
return { ...state, search: [] };
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unhandled action type: ${action.type}`);
|
throw new Error(`Unhandled action type: ${action.type}`);
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +20,7 @@ export const appReducer = (state: AppState, action: Action) => {
|
||||||
|
|
||||||
export type Action = {
|
export type Action = {
|
||||||
payload?: any;
|
payload?: any;
|
||||||
type: "setMeal" | "fetchMeal" | "fetchRandomMeal" | "toggleFav";
|
type: "setMeal" | "setSearchResults" | "clearSearchResults";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Dispatch = (action: Action) => void;
|
export type Dispatch = (action: Action) => void;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue