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 "./index.css";
|
||||
import MainLayout from "./layouts/MainLayout";
|
||||
import { AppRouter } from "./router";
|
||||
import { Router } from "./router/Router";
|
||||
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[],
|
||||
});
|
||||
|
||||
return loading ? (
|
||||
<div className="container center-align valign-wrapper">
|
||||
|
|
@ -20,12 +15,8 @@ export const App: FC = () => {
|
|||
</div>
|
||||
) : (
|
||||
<Router>
|
||||
<MainLayout
|
||||
searchString={searchString}
|
||||
setSearchResults={setSearchResults}
|
||||
setSearchString={setSearchString}
|
||||
>
|
||||
<AppRouter searchString={searchString} searchResults={searchResults} />
|
||||
<MainLayout>
|
||||
<AppRouter />
|
||||
</MainLayout>
|
||||
</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 { getData } from "../services/api";
|
||||
import { MealSummary } from "../types/meal";
|
||||
import { useMeal } from "../store/meal";
|
||||
import { fetchSearchResults } from "../store/meal/async";
|
||||
|
||||
type Props = {
|
||||
searchString: string;
|
||||
setSearchString: React.Dispatch<React.SetStateAction<string>>;
|
||||
setSearchResults: React.Dispatch<
|
||||
React.SetStateAction<{ meals: MealSummary[] }>
|
||||
>;
|
||||
};
|
||||
|
||||
export const SearchBar: FC<Props> = ({
|
||||
searchString,
|
||||
setSearchString,
|
||||
setSearchResults,
|
||||
}) => {
|
||||
export const SearchBar: FC = () => {
|
||||
const { dispatch } = useMeal();
|
||||
const [searchString, setSearchString] = useState("");
|
||||
const getSearchResults: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
searchString === ""
|
||||
? e.preventDefault()
|
||||
: getData(searchString, setSearchResults, "search");
|
||||
: fetchSearchResults(dispatch, searchString);
|
||||
};
|
||||
|
||||
const clearSearchBar = () => {
|
||||
setSearchString("");
|
||||
setSearchResults({ meals: [] });
|
||||
dispatch({ type: "clearSearchResults" });
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
|
|||
|
|
@ -6,29 +6,26 @@ import { SearchResult } from "./SearchResult";
|
|||
|
||||
type Props = {
|
||||
searchString: string;
|
||||
searchResults: { meals: MealSummary[] };
|
||||
searchResults: MealSummary[];
|
||||
};
|
||||
|
||||
export const SearchPage: FC<Props> = ({ searchString, searchResults }) => {
|
||||
const { meals } = searchResults;
|
||||
return (
|
||||
<PageLayout title={`Results for: ${searchString}`}>
|
||||
{!meals ? (
|
||||
<div className="center-align">
|
||||
<p>
|
||||
No results to display, instead there is a picture of my breakfast.
|
||||
</p>
|
||||
<img src={BreakfastImage} alt="Nothing here!" width="70%" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
<ul>
|
||||
{meals.map((meal, i) => (
|
||||
<SearchResult key={i} meal={meal} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
export const SearchPage: FC<Props> = ({ searchString, searchResults }) => (
|
||||
<PageLayout title={`Results for: ${searchString}`}>
|
||||
{!searchResults ? (
|
||||
<div className="center-align">
|
||||
<p>
|
||||
No results to display, instead there is a picture of my breakfast.
|
||||
</p>
|
||||
<img src={BreakfastImage} alt="Nothing here!" width="70%" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
<ul>
|
||||
{searchResults.map((meal, i) => (
|
||||
<SearchResult key={i} meal={meal} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</PageLayout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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 { SearchBar } from "../components/SearchBar";
|
||||
import { SideNav } from "../components/SideNav";
|
||||
import { MealSummary } from "../types/meal";
|
||||
|
||||
type Props = {
|
||||
searchString: string;
|
||||
setSearchString: React.Dispatch<React.SetStateAction<string>>;
|
||||
setSearchResults: React.Dispatch<
|
||||
React.SetStateAction<{ meals: MealSummary[] }>
|
||||
>;
|
||||
};
|
||||
const MainLayout: FC<Props> = ({
|
||||
searchString,
|
||||
setSearchString,
|
||||
setSearchResults,
|
||||
children,
|
||||
}) => {
|
||||
const MainLayout: FC = ({ children }) => {
|
||||
const [showNav, setShowNav] = useState(false);
|
||||
|
||||
const openNavClick: React.MouseEventHandler = (e) => {
|
||||
|
|
@ -42,12 +29,7 @@ const MainLayout: FC<Props> = ({
|
|||
<>
|
||||
<header>
|
||||
<Navbar openNavClick={openNavClick} />
|
||||
|
||||
<SearchBar
|
||||
searchString={searchString}
|
||||
setSearchString={setSearchString}
|
||||
setSearchResults={setSearchResults}
|
||||
/>
|
||||
<SearchBar />
|
||||
<SideNav showNav={showNav} closeNavClick={closeNavClick} />
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { FC } from "react";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
};
|
||||
type Props = { title: string };
|
||||
|
||||
const PageLayout: FC<Props> = ({ title, children }) => (
|
||||
<div className="container">
|
||||
|
|
|
|||
|
|
@ -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<Props> = ({ searchString, searchResults }) => (
|
||||
const AppRouter: FC = () => (
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Home />
|
||||
|
|
@ -37,7 +32,7 @@ const AppRouter: FC<Props> = ({ searchString, searchResults }) => (
|
|||
</Route>
|
||||
|
||||
<Route exact path="/search">
|
||||
<Search searchString={searchString} searchResults={searchResults} />
|
||||
<Search />
|
||||
</Route>
|
||||
|
||||
<Route path="/contact">
|
||||
|
|
|
|||
|
|
@ -23,3 +23,20 @@ export const fetchMeal = async (dispatch: Dispatch, id: string) => {
|
|||
|
||||
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
|
||||
|
||||
import { createContext, FC, useContext, useReducer } from "react";
|
||||
import { MealApi } from "../../types/meal";
|
||||
import { MealApi, MealSummary } from "../../types/meal";
|
||||
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;
|
||||
|
||||
const AppContext = createContext<ContextType>(undefined);
|
||||
|
|
@ -18,7 +29,7 @@ export const useMeal = () => {
|
|||
};
|
||||
|
||||
export const AppProvider: FC = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(appReducer, { meals: [] as MealApi[] });
|
||||
const [state, dispatch] = useReducer(appReducer, initState);
|
||||
const value = { state, dispatch };
|
||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@ import { AppState } from "./index";
|
|||
export const appReducer = (state: AppState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case "setMeal":
|
||||
return { meals: action.payload };
|
||||
case "fetchMeal":
|
||||
return { meals: state.meals };
|
||||
case "fetchRandomMeal":
|
||||
return { meals: state.meals };
|
||||
case "toggleFav":
|
||||
return { meals: state.meals };
|
||||
return { ...state, meals: action.payload };
|
||||
case "setSearchResults":
|
||||
return {
|
||||
...state,
|
||||
search: action.payload.search,
|
||||
searchString: action.payload.searchString,
|
||||
};
|
||||
case "clearSearchResults":
|
||||
return { ...state, search: [] };
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
|
|
@ -18,6 +20,7 @@ export const appReducer = (state: AppState, action: Action) => {
|
|||
|
||||
export type Action = {
|
||||
payload?: any;
|
||||
type: "setMeal" | "fetchMeal" | "fetchRandomMeal" | "toggleFav";
|
||||
type: "setMeal" | "setSearchResults" | "clearSearchResults";
|
||||
};
|
||||
|
||||
export type Dispatch = (action: Action) => void;
|
||||
|
|
|
|||
Loading…
Reference in a new issue