Start on using context api, preserving state and will allow for moving some thing inline to components without prop drilling.

develop
Thomas Lynch 2 years ago
parent cf3176f811
commit 6257420ef0
  1. 14
      api.js
  2. 43
      pages/_app.js
  3. 53
      pages/account.js
  4. 15
      providers/GlobalProvider.js
  5. 14
      providers/GlobalReducer.js

@ -17,7 +17,7 @@ function buildOptions(route, method, body) {
return options;
}
export default async function ApiCall(route, method='get', body, stateCallback, errorCallback, finishProgress, router) {
export default async function ApiCall(route, method='get', body, dispatch, finishProgress, router) {
// Start progress bar
NProgress.start();
@ -40,7 +40,7 @@ export default async function ApiCall(route, method='get', body, stateCallback,
}
if (!response) {
errorCallback && errorCallback('An error occurred');
dispatch && dispatch({ type: 'error', payload: 'An error occurred' });
return;
}
@ -51,12 +51,16 @@ export default async function ApiCall(route, method='get', body, stateCallback,
if (response.redirect) {
return router.push(response.redirect, null, { scroll: false });
} else if (response.error) {
errorCallback && errorCallback(response.error);
dispatch && dispatch({ type: 'error', payload: response.error });
return;
}
stateCallback && stateCallback(response);
dispatch && dispatch({ type: 'state', payload: response });
} else {
errorCallback && errorCallback('An error occurred');
dispatch && dispatch({ type: 'error', payload: 'An error occurred' });
}
}
export async function getAccount(dispatch, finishProgress, router) {
return ApiCall('/account.json', 'GET', null, dispatch, finishProgress, router)
}

@ -2,28 +2,31 @@ import 'bootstrap/dist/css/bootstrap.css';
import NProgress from 'nprogress';
import Layout from '../components/Layout.js';
import "nprogress/nprogress.css";
import GlobalProvider from '../providers/GlobalProvider.js';
export default function App({ Component, pageProps }) {
return (
<Layout>
<style>
{`
html, body { font-family: arial,helvetica,sans-serif; height: 100%; }
.green { color: green; }
.red { color: red; }
footer { margin-top: auto; }
.btn { font-weight: bold; }
@media (prefers-color-scheme: dark) {
:root { --bs-body-color: #fff; --bs-body-bg: #000000; }
.text-muted, a, a:visited, a:hover, .nav-link, .nav-link:hover { color:#fff!important; }
.list-group-item { color: #fff; background-color: #111111; }
input:not(.btn), option, select.form-select { color: #fff!important; background-color: #111111!important; border: 1px solid black!important; }
.list-group-item-action:focus, .list-group-item-action:hover { color: #fff; background-color: #1F1F1F; }
.table { color: #fff; border-color: transparent !important; }
}
`}
</style>
<Component {...pageProps} />
</Layout>
<GlobalProvider>
<Layout>
<style>
{`
html, body { font-family: arial,helvetica,sans-serif; height: 100%; }
.green { color: green; }
.red { color: red; }
footer { margin-top: auto; }
.btn { font-weight: bold; }
@media (prefers-color-scheme: dark) {
:root { --bs-body-color: #fff; --bs-body-bg: #000000; }
.text-muted, a, a:visited, a:hover, .nav-link, .nav-link:hover { color:#fff!important; }
.list-group-item { color: #fff; background-color: #111111; }
input:not(.btn), option, select.form-select { color: #fff!important; background-color: #111111!important; border: 1px solid black!important; }
.list-group-item-action:focus, .list-group-item-action:hover { color: #fff; background-color: #1F1F1F; }
.table { color: #fff; border-color: transparent !important; }
}
`}
</style>
<Component {...pageProps} />
</Layout>
</GlobalProvider>
);
}

@ -1,36 +1,40 @@
import React, { useState } from 'react';
import React, { useState, useContext, useEffect, useMemo } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import MapLink from '../components/MapLink.js';
import LoadingPlaceholder from '../components/LoadingPlaceholder.js';
import ErrorAlert from '../components/ErrorAlert.js';
import ApiCall from '../api.js';
import { getAccount } from '../api.js';
import { useRouter } from 'next/router';
import { GlobalContext } from '../providers/GlobalProvider.js';
const Account = (props) => {
const router = useRouter();
const [accountData, setAccountData] = useState(props);
const [error, setError] = useState();
React.useEffect(() => {
if (!accountData.user) {
ApiCall('/account.json', 'GET', null, setAccountData, setError, null, router);
}
}, [accountData.user, router]);
const [state, dispatch] = useContext(GlobalContext);
// Set into context from props (From getServerSideProps), else make API call
useEffect(() => {
if (props.user != null) {
dispatch({ type: 'state', payload: props });
} else {
getAccount(dispatch, null, router);
}
}, [dispatch, props, router]);
const loadingSection = (
<div className="list-group-item list-group-item-action d-flex align-items-start">
<LoadingPlaceholder />
</div>
);
const loadingSection = useMemo(() => {
return (
<div className="list-group-item list-group-item-action d-flex align-items-start">
<LoadingPlaceholder />
</div>
);
}, []);
let innerData;
if (accountData.user != null) {
if (state.user != null) {
const { user, maps, acls, globalAcl, csrf } = accountData;
const { user, maps, acls, globalAcl, csrf } = state;
// isAdmin for showing global override option
const isAdmin = user.username === 'admin';
@ -43,14 +47,14 @@ const Account = (props) => {
async function switchCluster(e) {
e.preventDefault();
await ApiCall('/forms/cluster', 'POST', JSON.stringify({ _csrf: csrf, cluster: nextCluster }), null, setError, 0.5, router);
await ApiCall('/account.json', 'GET', null, setAccountData, setError, null, router);
await ApiCall('/forms/cluster', 'POST', JSON.stringify({ _csrf: csrf, cluster: nextCluster }), null, 0.5, router);
await ApiCall('/account.json', 'GET', null, dispatch, null, router);
}
async function toggleGlobal(e) {
e.preventDefault();
await ApiCall('/forms/global/toggle', 'POST', JSON.stringify({ _csrf: csrf }), null, setError, 0.5, router);
await ApiCall('/account.json', 'GET', null, setAccountData, setError, null, router);
await ApiCall('/forms/global/toggle', 'POST', JSON.stringify({ _csrf: csrf }), null, 0.5, router);
await ApiCall('/account.json', 'GET', null, dispatch, null, router);
}
innerData = (
@ -138,10 +142,9 @@ const Account = (props) => {
} else {
innerData = (
<>
{Array(9).fill(loadingSection)}
{Array(9).map((_, i) => <loadingSection key={i}/>)}
</>
);
@ -154,7 +157,7 @@ const Account = (props) => {
<title>Account</title>
</Head>
{error && <ErrorAlert error={error} />}
{state.error && <ErrorAlert error={state.error} />}
<h5 className="fw-bold">
Controls:

@ -0,0 +1,15 @@
import { createContext, useReducer } from 'react';
import reducer from './GlobalReducer.js';
export const GlobalContext = createContext();
const initialState = {};
export default function GlobalProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<GlobalContext.Provider value={[state, dispatch]}>
{props.children}
</GlobalContext.Provider>
);
}

@ -0,0 +1,14 @@
// reducer.js
export default function reducer(state, action) {
//could do somethig na bit more smart than this
switch (action.type) {
case 'error':
// Add error, keep existing state
return { error: action.payload, ...state };
case 'state':
// Keep state, overwrite or add new values from payload, and null error
return { ...state, ...action.payload, error: null };
default:
throw new Error();
}
}
Loading…
Cancel
Save