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

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

View file

@ -6,14 +6,12 @@ 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;
return (
<PageLayout title={`Results for: ${searchString}`}> <PageLayout title={`Results for: ${searchString}`}>
{!meals ? ( {!searchResults ? (
<div className="center-align"> <div className="center-align">
<p> <p>
No results to display, instead there is a picture of my breakfast. No results to display, instead there is a picture of my breakfast.
@ -23,12 +21,11 @@ export const SearchPage: FC<Props> = ({ searchString, searchResults }) => {
) : ( ) : (
<div className="row"> <div className="row">
<ul> <ul>
{meals.map((meal, i) => ( {searchResults.map((meal, i) => (
<SearchResult key={i} meal={meal} /> <SearchResult key={i} meal={meal} />
))} ))}
</ul> </ul>
</div> </div>
)} )}
</PageLayout> </PageLayout>
); );
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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