async actions

This commit is contained in:
Ruidy 2021-04-05 11:39:47 +02:00
parent e4d6c31128
commit c19bb6ac32
10 changed files with 90 additions and 105 deletions

View file

@ -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>
);

View file

@ -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>) => {

View file

@ -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>
);

View file

@ -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} />
);

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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 },
});
};

View file

@ -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>;
};

View file

@ -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;