Skip to content

Commit

Permalink
Context API Refactor (#29)
Browse files Browse the repository at this point in the history
* context-refactor

* Adding currentFilters, setCurrentFilters to context

* refactored results props

* removed setIsFilterOpenProp from results

* removed console.logs

* Refactoring Header.tsx and Header.test.tsx to use context hooks.

* WIP: FAILING - refactoring handleFilterChange to context

* Refactored handleFilterChange to be global in context, and created a helper function for applying changes.
- Created new tests for applying filters
- Fixed existing tests which used context

* Adding handleClearFilters to context and using it in FilterBar

Co-authored-by: David Q <[email protected]>
  • Loading branch information
adamos486 and caramelqq authored Sep 11, 2021
1 parent a1de2eb commit eb8f717
Show file tree
Hide file tree
Showing 14 changed files with 10,391 additions and 10,628 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@types/react-dom": "^16.9.0",
"axios": "^0.19.2",
"moment": "^2.27.0",
"prettier": "^2.3.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
Expand Down
73 changes: 43 additions & 30 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,74 @@
import React, {useEffect, useState, useMemo} from "react";
import React, {useEffect, useState, useMemo, ChangeEvent} from "react";
import "./App.css";
import {Router, Route, Switch} from "react-router-dom";
import { createBrowserHistory } from "history";
import {createBrowserHistory} from "history";

import Home from "./components/home/Home";
import Header from "./components/Header";
import Results from "./components/results/Results";
import Donate from "./components/donate/Donate";
import {ThemeProvider} from "@material-ui/core/styles";
import {theme} from "./theme";
import { getResults } from "./api/axiosApi";
import {applyFilters} from "./components/results/filterHelpers";
import { CurrentFilters, Result } from "./types";
import {getResults} from "./api/axiosApi";
import {applyFilters, applyFilterChanges} from "./components/results/filterHelpers";
import {CurrentFilters, GlobalStateContextType, Result} from "./types";
import {setValues} from "./context/globalStates";


const history = createBrowserHistory();


const App = () => {
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [initialData, setInitialData] = useState<Result[]>([]);
const [data, setData] = useState<Result[]>([]);
const [currentFilters, setCurrentFilters] = useState<CurrentFilters>({});
const [filteredResults, setFilteredResults] = useState<Result[]>([]);

const filteredResults = useMemo(() => applyFilters(initialData, currentFilters), [
currentFilters,
initialData,
]);
const handleClearFilters = () => setCurrentFilters({});

const handleFilterChange = (group: keyof CurrentFilters) => (event: ChangeEvent<HTMLInputElement>) => {
applyFilterChanges(
event.target.checked,
event.target.name,
group,
currentFilters,
setCurrentFilters
);
};

useMemo(
() => setFilteredResults(applyFilters(data, currentFilters)),
[currentFilters, setFilteredResults, data]
);

const definedStateValues = {
setCurrentFilters: setCurrentFilters,
currentFilters: currentFilters,
handleFilterChange: handleFilterChange,
setIsFilterOpen: setIsFilterOpen,
isFilterOpen: isFilterOpen,
setInitialData: setData,
initialData: data,
filteredResults: filteredResults,
setFilteredResults: setFilteredResults,
handleClearFilters: handleClearFilters
} as GlobalStateContextType;
setValues(definedStateValues);

useEffect(() => {
getResults()
.then(setInitialData);
.then(setData);
}, []);

return (
<ThemeProvider theme={theme}>
<div className="App">
<Router history={history}>
<Header
isFilterOpen={isFilterOpen}
setIsFilterOpen={setIsFilterOpen}
currentFilters={currentFilters}
setCurrentFilters={setCurrentFilters}
filteredResults={filteredResults}
/>
<Header/>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/results">
<Results
isFilterOpen={isFilterOpen}
setIsFilterOpen={setIsFilterOpen}
currentFilters={currentFilters}
setCurrentFilters={setCurrentFilters}
setResults={setInitialData}
filteredResults={filteredResults}
/>
</Route>
<Route exact path="/donate" component={Donate} />
<Route exact path="/" component={Home}/>
<Route exact path="/results" component={Results}/>
<Route exact path="/donate" component={Donate}/>
</Switch>
</Router>
</div>
Expand Down
18 changes: 11 additions & 7 deletions src/components/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ import {Router} from "react-router-dom";
import {createMemoryHistory} from "history";
import {act} from "react-dom/test-utils";
import { Result, CurrentFilters } from "../types";
import {setValues} from "../context/globalStates";

describe("Header", () => {
const history = createMemoryHistory();

const HeaderWrapper = () => {
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [currentFilters, setCurrentFilters] = useState<CurrentFilters>({});
const filteredResults = [] as Result[];

setValues({
setIsFilterOpen,
isFilterOpen,
setCurrentFilters,
currentFilters
});

return (
<Router history={history}>
<Header
isFilterOpen={isFilterOpen}
setIsFilterOpen={setIsFilterOpen}
currentFilters={currentFilters}
setCurrentFilters={setCurrentFilters}
filteredResults={filteredResults}
/>
<Header />
</Router>
);
};
Expand Down
107 changes: 38 additions & 69 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import React, {ChangeEvent, useState, useEffect} from "react";
import React, {useContext, useEffect, useState} from "react";
import styled, {StyledComponent} from "styled-components";
import {Link} from "react-router-dom";
import {useLocation} from "react-router-dom";
import {Link, useLocation} from "react-router-dom";
import {ReactComponent as Logo} from "../assets/Logo.svg";

import {useHistory} from "react-router-dom";

import {FilterBar} from "./results/FilterBar";

import Typography from "@material-ui/core/Typography";
import MatMenu from '@material-ui/core/Menu';
import MatMenuItem from '@material-ui/core/MenuItem';
import MatButton from '@material-ui/core/Button';
import MenuIcon from '@material-ui/icons/Menu';
import MatMenu from "@material-ui/core/Menu";
import MatMenuItem from "@material-ui/core/MenuItem";
import MatButton from "@material-ui/core/Button";
import Button from "@material-ui/core/Button";
import MenuIcon from "@material-ui/icons/Menu";
import Drawer from "@material-ui/core/Drawer";

import {HeaderProps, CurrentFilters} from '../types';

import {GlobalStateContext} from "../context/globalStates";
import {grabCurrentFiltersFromURLParams} from "../util/historyHelper";

const WhiteContainer = styled.header`
top: 0;
Expand Down Expand Up @@ -90,27 +86,27 @@ const MenuItem = styled(Link)`
}
`;

const Header: React.FC<HeaderProps> = ({
setIsFilterOpen,
isFilterOpen,
currentFilters,
const Header: React.FC = () => {
const {
setIsFilterOpen,
setCurrentFilters,
filteredResults,
}) => {
} = useContext(GlobalStateContext);

const location = useLocation();
const [prevScrollPos, setPrevScrollPos] = useState(0);
const [visible, setVisible] = useState(true);
const [isResultsPage, setIsResultsPage] = useState(false);
const [filterToggle, setFilterToggle] = React.useState(false);

const history = useHistory<{currentFilters: CurrentFilters}>();
if(history.location.state && history.location.state.currentFilters){
setCurrentFilters(history.location.state.currentFilters);
}
useEffect(() => {
if (location.search) {
setCurrentFilters(grabCurrentFiltersFromURLParams(location));
}
}, [setCurrentFilters, location]);

// Update isResultsPage when location pathname changes;
useEffect(() => {
setIsResultsPage(location.pathname === '/results');
setIsResultsPage(location.pathname === "/results");
}, [location.pathname]);

// Handle navbar layout when scrolling
Expand Down Expand Up @@ -164,30 +160,8 @@ const Header: React.FC<HeaderProps> = ({
setAnchorEl(null);
};

const handleFilterChange = (group: keyof CurrentFilters) => (
event: ChangeEvent<HTMLInputElement>
) => {
const newFilters = {...currentFilters};
if (event.target.checked) {
if (group in newFilters) {
if (!newFilters[group]?.includes(event.target.name)) {
newFilters[group]?.push(event.target.name);
}
} else {
newFilters[group] = [event.target.name];
}
} else {
const index = newFilters[group]?.indexOf(event.target.name);
if (index >= 0) newFilters[group]?.splice(index, 1);
if (newFilters[group]?.length === 0) delete newFilters[group];
}
setCurrentFilters(newFilters);
};

const handleClearFilters = () => setCurrentFilters({});

const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
if (event.type === 'keydown') {
if (event.type === "keydown") {
return;
}
setIsFilterOpen(open);
Expand Down Expand Up @@ -215,8 +189,8 @@ const Header: React.FC<HeaderProps> = ({
pathname: "/",
search: "",
hash: "#about",
state: {toAbout: true},
}} onClick={handleClose}>
state: {toAbout: true}
}} onClick={handleClose}>
About
</MatMenuItem>
</MatMenu>
Expand All @@ -226,27 +200,22 @@ const Header: React.FC<HeaderProps> = ({
return (
<Container>
<LogoWrapper>
<Logo role="logo" />
<Logo role="logo"/>
</LogoWrapper>
{isResultsPage &&
<FilterContainer>
<Button onClick={toggleDrawer(true)}>{'filter'}</Button>
<Drawer anchor={'left'} open={filterToggle} onClose={toggleDrawer(false)}>
<div
role="presentation"
onClick={toggleDrawer(false)}
onKeyDown={toggleDrawer(false)}
style={{ width: "400 px" }}
>
<FilterBar
currentFilters={currentFilters}
onChange={handleFilterChange}
onClear={handleClearFilters}
isFilterOpen={isFilterOpen}
/>
</div>
</Drawer>
</FilterContainer>
<FilterContainer>
<Button onClick={toggleDrawer(true)}>{"filter"}</Button>
<Drawer anchor={"left"} open={filterToggle} onClose={toggleDrawer(false)}>
<div
role="presentation"
onClick={toggleDrawer(false)}
onKeyDown={toggleDrawer(false)}
style={{width: "400 px"}}
>
<FilterBar />
</div>
</Drawer>
</FilterContainer>
}
<Menu role="menu">
{SmallMenu()}
Expand All @@ -255,7 +224,7 @@ const Header: React.FC<HeaderProps> = ({
pathname: "/",
search: "",
hash: "",
state: {toHome: true},
state: {toHome: true}
}}
>
<Typography variant="body1">Home</Typography>
Expand All @@ -268,7 +237,7 @@ const Header: React.FC<HeaderProps> = ({
pathname: "/",
search: "",
hash: "#about",
state: {toAbout: true},
state: {toAbout: true}
}}
>
<Typography variant="body1">About</Typography>
Expand Down
12 changes: 12 additions & 0 deletions src/components/home/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@ import {fireEvent, render, screen} from "@testing-library/react";
import Home from "./Home";
import {Route, Router} from "react-router-dom";
import {createMemoryHistory} from "history";
import { setValues } from "../../context/globalStates";

const history = createMemoryHistory();
jest.spyOn(history, "push");

beforeEach(() => {
setValues({
setCurrentFilters: jest.fn(),
currentFilters: {},
handleFilterChange: jest.fn(),
setIsFilterOpen: jest.fn(),
isFilterOpen: true,
setInitialData: jest.fn(),
initialData: [],
filteredResults: [],
handleClearFilters: jest.fn()
});
render(
<Router history={history}>
<Route component={Home} />
Expand Down
Loading

0 comments on commit eb8f717

Please sign in to comment.