Fix/separate appstate (#33)

* add: appState component for wrapping the state
* add: viewMode to pageState
* add: wrapp everything with the pageState at top level
This commit is contained in:
Johannes Kirschbauer 2023-04-27 19:11:40 +02:00 committed by GitHub
parent ca48535b85
commit 39ccbcd763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 128 additions and 98 deletions

View File

@ -0,0 +1,73 @@
import React, { useEffect, useState } from "react";
import { NextRouter, useRouter } from "next/router";
import { LinearProgress } from "@mui/material";
import {
InitialPageState,
initialPageState,
PageState,
} from "../../models/internals";
import { PageContextProvider } from "../pageContext";
const getInitialProps = async (context: NextRouter) => {
const { query } = context;
const initialProps = { ...initialPageState };
Object.entries(query).forEach(([key, value]) => {
if (value && !Array.isArray(value)) {
try {
const parsedValue = JSON.parse(value) as never;
const initialValue = initialPageState[key as keyof InitialPageState];
if (!initialValue || typeof parsedValue === typeof initialValue) {
initialProps[key as keyof InitialPageState] = JSON.parse(
value
) as never;
} else {
throw "Type of query param does not match the initial values type";
}
} catch (error) {
console.error("Invalid query:", { key, value, error });
}
}
});
const FOTD = Object.entries(query).length === 0;
return {
props: {
...initialProps,
FOTD,
},
};
};
interface AppStateProps {
children: React.ReactNode;
}
export function AppState(props: AppStateProps) {
const { children } = props;
const router = useRouter();
const [initialProps, setInitialProps] = useState<PageState | null>(null);
useEffect(() => {
if (router.isReady && initialProps === null) {
getInitialProps(router).then((r) => {
const { props } = r;
console.info("Url Query changed\n\nUpdating pageState with delta:", {
props,
});
setInitialProps((curr) => ({ ...curr, ...props }));
});
}
}, [router, initialProps]);
return (
<>
{!initialProps ? (
<LinearProgress />
) : (
<PageContextProvider pageProps={initialProps}>
{children}
</PageContextProvider>
)}
</>
);
}

View File

@ -0,0 +1 @@
export { AppState } from "./appState";

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from "react";
import React, { useMemo } from "react";
import { Box, List, ListItem, Stack, TablePagination } from "@mui/material";
import { BasicDataViewProps } from "../../types/basicDataView";
import { SearchInput } from "../searchInput";
@ -9,6 +9,7 @@ import { useMobile } from "../layout/layout";
import { EmptyRecordsPlaceholder } from "../emptyRecordsPlaceholder";
import { FunctionOfTheDay } from "../functionOfTheDay";
import { Query, SearchOptions } from "minisearch";
import { ViewMode } from "../../models/internals";
export type BasicListItem = {
item: React.ReactNode;
@ -19,15 +20,13 @@ export type BasicListProps = BasicDataViewProps & {
search: (query: Query, options?: SearchOptions) => void;
};
type ViewMode = "explore" | "browse";
export function BasicList(props: BasicListProps) {
const { items, search } = props;
const { pageState, setPageStateVariable, resetQueries } = usePageContext();
const isMobile = useMobile();
const { page, itemsPerPage, filter, term, FOTD, data } = pageState;
const [mode, setMode] = useState<ViewMode>("explore");
const { page, itemsPerPage, FOTD: showFunctionOfTheDay, data } = pageState;
const setViewMode = setPageStateVariable<ViewMode>("viewMode");
const setPage = setPageStateVariable<number>("page");
const setTerm = setPageStateVariable<string>("term");
const setFilter = setPageStateVariable<Filter>("filter");
@ -66,12 +65,6 @@ export function BasicList(props: BasicListProps) {
setPage(1);
};
const showFunctionOfTheDay =
mode === "explore" &&
filter.to === "any" &&
filter.from === "any" &&
term === "" &&
FOTD === true;
return (
<Stack>
<SearchInput
@ -80,15 +73,16 @@ export function BasicList(props: BasicListProps) {
placeholder="search nix functions"
handleSearch={handleSearch}
/>
{showFunctionOfTheDay ? (
{showFunctionOfTheDay && (
<FunctionOfTheDay
data={data}
handleClose={() => {
setMode("browse");
setViewMode("browse");
}}
/>
) : (
)}
{!showFunctionOfTheDay && (
<List aria-label="basic-list" sx={{ pt: 0 }}>
{items.length ? (
pageItems.map(({ item, key }, idx) => (

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { NextRouter, useRouter } from "next/router";
import {
InitialPageState,
@ -57,6 +57,7 @@ export const PageContextProvider = (props: PageContextProviderProps) => {
const router = useRouter();
const { children, pageProps } = props;
const [pageState, setPageState] = useState<PageState>(pageProps);
const { term, filter, viewMode } = pageState;
function setPageStateVariable<T>(field: keyof InitialPageState) {
return function (value: React.SetStateAction<T> | T) {
if (typeof value !== "function") {
@ -82,9 +83,25 @@ export const PageContextProvider = (props: PageContextProviderProps) => {
}
setPageState((curr) => ({ ...curr, ...initialPageState }));
}
useEffect(() => {
setPageState((c) => ({
...c,
FOTD:
viewMode === "explore" &&
filter.to === "any" &&
filter.from === "any" &&
term === "",
}));
}, [viewMode, filter, term]);
return (
<PageContext.Provider
value={{ pageState, setPageState, setPageStateVariable, resetQueries }}
value={{
pageState,
setPageState,
setPageStateVariable,
resetQueries,
}}
>
{children}
</PageContext.Provider>

View File

@ -37,8 +37,6 @@ export function SearchInput(props: SearchInputProps) {
const _handleClear = () => {
_setTerm("");
handleClear();
// handleFilter(initialPageState.filter);
// handleSubmit("");
};
const handleType = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>

View File

@ -1,17 +1,20 @@
import { data } from "./data";
import { MetaData, NixType } from "./nix";
export type ViewMode = "explore" | "browse";
export type ComputedState = {
FOTD: boolean;
}
export type PageState = {
data: MetaData;
selected: string | null;
term: string;
filter: Filter;
page: number;
itemsPerPage: number;
data: MetaData;
selected: string | null;
term: string;
filter: Filter;
page: number;
itemsPerPage: number;
viewMode: ViewMode;
} & ComputedState;
export type InitialPageState = Omit<PageState, keyof ComputedState>;
@ -23,6 +26,7 @@ export const initialPageState: InitialPageState = {
filter: { from: "any", to: "any" },
page: 1,
itemsPerPage: 10,
viewMode: "explore"
};
export type Filter = { to: NixType; from: NixType };

View File

@ -15,6 +15,7 @@ import { lightThemeOptions, darkThemeOptions } from "../styles/theme";
import "../styles/globals.css";
import { Layout } from "../components/layout";
import Head from "next/head";
import { AppState } from "../components/appState";
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
@ -49,7 +50,12 @@ const MyApp: React.FunctionComponent<MyAppProps> = (props) => {
<meta />
<meta name="robots" content="all" />
<link rel="icon" href="/favicon.png" />
<link rel="search" type="application/opensearchdescription+xml" title="Search nix function on noogle" href="/search.xml"></link>
<link
rel="search"
type="application/opensearchdescription+xml"
title="Search nix function on noogle"
href="/search.xml"
></link>
</Head>
<CacheProvider value={emotionCache}>
@ -59,7 +65,7 @@ const MyApp: React.FunctionComponent<MyAppProps> = (props) => {
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
maxSnack={1}
>
{getContent()}
<AppState>{getContent()}</AppState>
</SnackbarProvider>
</ThemeProvider>
</CacheProvider>

View File

@ -1,77 +1,14 @@
import React, { useEffect, useState } from "react";
import {
initialPageState,
PageState,
InitialPageState,
} from "../models/internals";
import { PageContext, PageContextProvider } from "../components/pageContext";
import React from "react";
import { usePageContext } from "../components/pageContext";
import NixFunctions from "../components/NixFunctions";
import { NextRouter, useRouter } from "next/router";
import { LinearProgress } from "@mui/material";
const getInitialProps = async (context: NextRouter) => {
const { query } = context;
const initialProps = { ...initialPageState };
Object.entries(query).forEach(([key, value]) => {
if (value && !Array.isArray(value)) {
try {
const parsedValue = JSON.parse(value) as never;
const initialValue = initialPageState[key as keyof InitialPageState];
if (!initialValue || typeof parsedValue === typeof initialValue) {
initialProps[key as keyof InitialPageState] = JSON.parse(
value
) as never;
} else {
throw "Type of query param does not match the initial values type";
}
} catch (error) {
console.error("Invalid query:", { key, value, error });
}
}
});
const FOTD = Object.entries(query).length === 0;
return {
props: {
...initialProps,
FOTD,
},
};
};
export default function FunctionsPage() {
const router = useRouter();
const [initialProps, setInitialProps] = useState<PageState | null>(null);
useEffect(() => {
if (router.isReady && initialProps === null) {
getInitialProps(router).then((r) => {
const { props } = r;
console.debug("Url Query changed\n\nUpdating pageState with delta:", {
props,
});
setInitialProps((curr) => ({ ...curr, ...props }));
});
}
}, [router, initialProps]);
const { pageState, setPageStateVariable } = usePageContext();
return (
<>
{!initialProps ? (
<LinearProgress />
) : (
<PageContextProvider pageProps={initialProps}>
<PageContext.Consumer>
{(context) => (
<NixFunctions
pageState={context.pageState}
setPageStateVariable={context.setPageStateVariable}
/>
)}
</PageContext.Consumer>
</PageContextProvider>
)}
</>
<NixFunctions
pageState={pageState}
setPageStateVariable={setPageStateVariable}
/>
);
}