Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React query implementation #2087

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"eslint:recommended",
"plugin:react/recommended",
"react-app",
"plugin:jsx-a11y/recommended"
"plugin:jsx-a11y/recommended",
"plugin:@tanstack/query/recommended" // Extend the recommended rules from the Query plugin
],
"globals": {
"process": true
Expand All @@ -30,12 +31,13 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "react-hooks", "jest", "jsx-a11y"],
"plugins": ["react", "react-hooks", "jest", "jsx-a11y", "@tanstack/query"],
"rules": {
// suppress errors for missing 'import React' in files
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"jsx-a11y/no-autofocus": "off",

"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/mouse-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
Expand Down
616 changes: 397 additions & 219 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"@babel/preset-react": "^7.24.7",
"@n8tb1t/use-scroll-position": "^2.0.3",
"@testing-library/dom": "^10.4.0",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-query-devtools": "^5.59.0",
"@uiw/codemirror-extensions-langs": "^4.23.3",
"@uiw/codemirror-theme-github": "^4.23.3",
"@uiw/react-codemirror": "^4.23.3",
Expand Down Expand Up @@ -89,6 +91,7 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.25.4",
"@tanstack/eslint-plugin-query": "^5.59.1",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
Expand Down
23 changes: 23 additions & 0 deletions src/QueryCover.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import React from "react";
// {/* uncomment below line during development */}
// import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const QueryCover = ({ children }) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
//cache stays fresh for 3min
staleTime: 180000,
},
},
});
return (
<QueryClientProvider client={queryClient}>
{children}
{/* uncomment below line during development */}
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
</QueryClientProvider>
);
};

export default QueryCover;
213 changes: 213 additions & 0 deletions src/Routes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import React, { lazy, Suspense, useEffect, useState } from "react";

Check warning on line 1 in src/Routes.jsx

View workflow job for this annotation

GitHub Actions / CI (19.x)

'useState' is defined but never used
import {
Navigate,
Route,
Routes,
useLocation,
useParams,
} from "react-router-dom";
import RedirectToCountry from "./routing/RedirectToCountry";
import AuthCallback from "./layout/AuthCallback";
import CountryIdLayout from "./routing/CountryIdLayout";
import Home from "./pages/Home";
import About from "./pages/About";
import Jobs from "./pages/Jobs";
import Testimonials from "./pages/Testimonials";
import CalculatorInterstitial from "./pages/CalculatorInterstitial";
import SimulationsPage from "./pages/Simulations";
import Research from "./pages/Research";
import BlogPage from "./pages/BlogPage";
import Donate from "./pages/Donate";
import PrivacyPage from "./pages/PrivacyPage";
import TACPage from "./pages/TermsAndConditions";
import RedirectBlogPost from "./routing/RedirectBlogPost";
import CitizensEconomicCouncil from "./applets/CitizensEconomicCouncil";
import ManifestosComparison from "./applets/ManifestosComparison";
import { StatusPage } from "./pages/StatusPage";
import TrafwaCalculator from "./applets/TrafwaCalculator";
import StateEitcsCtcs from "./applets/StateEitcsCtcs";
import CTCComparison from "./applets/CTCComparison";

import Header from "./layout/Header";
import LoadingCentered from "./layout/LoadingCentered";
import ErrorPage from "./layout/ErrorPage";
import APIDocumentationPage from "./pages/APIDocumentationPage";
import UserProfilePage from "./pages/UserProfilePage";
import useMetadata from "./hooks/useMetadata";
import usePolicy from "./hooks/usePolicy";
import { extractCountryId } from "./pages/policy/output/utils";
import useUserProfile from "./hooks/useUserProfile";
import { ConfigProvider } from "antd";
import style from "./style";
import CookieConsent from "./modals/CookieConsent";
import DeveloperLayout from "./pages/DeveloperLayout";
import DeveloperHome from "./pages/DeveloperHome";
import US2024ElectionCalculator from "./applets/US2024ElectionCalculator";

const PolicyPage = lazy(() => import("./pages/PolicyPage"));
const HouseholdPage = lazy(() => import("./pages/HouseholdPage"));

function ScrollToTop() {
const { pathname } = useLocation();

useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);

return null;
}

const PolicyEngineRoutes = () => {
const countryId = extractCountryId();
// If the path is /, redirect to /[countryId]
// If the path is /[countryId], render the homepage
// If the path is /[countryId]/about, render the about page
// If the path is /[countryId]/jobs, render the jobs page
// If the path is /[countryId]/research, render the research page
// If the path is not recognized, redirect to /[countryId]

return (
//TODO temporary will be shifting back to policy engine
<ConfigProvider
theme={{
token: {
borderRadius: 0,
colorPrimary: style.colors.BLUE,
fontFamily: "Roboto Serif",
},
}}
>
<ScrollToTop />
<CookieConsent />
{/* till here */}

<Routes>
{/* Redirect from / to /[countryId] */}
<Route path="/" element={<RedirectToCountry />} />
<Route path="/callback" element={<AuthCallback />} />
<Route path="/:countryId" element={<CountryIdLayout />}>
<Route index={true} element={<Home />} />
<Route path="about" element={<About />} />
<Route path="jobs" element={<Jobs />} />
<Route path="testimonials" element={<Testimonials />} />
<Route path="calculator" element={<CalculatorInterstitial />} />
<Route path="simulations" element={<SimulationsPage />} />
<Route path="developer-tools" element={<DeveloperLayout />}>
<Route index element={<DeveloperHome />} />
<Route path="simulations" element={<SimulationsPage />} />
<Route path="api_status" element={<StatusPage />} />
</Route>
<Route path="research" element={<Research />} />
<Route path="research/:postName" element={<BlogPage />} />
<Route path="donate" element={<Donate />} />
<Route path="privacy" element={<PrivacyPage />} />
<Route path="terms" element={<TACPage />} />

<Route path="household/*" element={<HouseholdPageLayout />} />
<Route path="policy/*" element={<PolicyPageLayout />} />

<Route path="profile" element={<UserProfileRoute />} />
<Route path="profile/:user_id" element={<UserProfilePage />} />
<Route path="api" element={<APIDocumentationPage />} />
{/* redirect from /countryId/blog/slug to /countryId/research/slug */}
<Route path="blog/:postName" element={<RedirectBlogPost />} />
</Route>
<Route path="/uk/cec" element={<CitizensEconomicCouncil />} />
<Route path="/uk/2024-manifestos" element={<ManifestosComparison />} />
<Route path="/:countryId/api_status" element={<StatusPage />} />
<Route
path="/us/trafwa-ctc-calculator"
element={<TrafwaCalculator />}
/>
<Route path="/us/state-eitcs-ctcs" element={<StateEitcsCtcs />} />
<Route
path="/us/child-tax-credit-2024-election-calculator"
element={<CTCComparison />}
/>
<Route
path="/us/2024-election-calculator"
element={<US2024ElectionCalculator />}
/>

{/* Redirect for unrecognized paths */}
<Route path="*" element={<Navigate to={`/${countryId}`} />} />
</Routes>
</ConfigProvider>
);
};

export default PolicyEngineRoutes;

export const LoadingPage = () => (
<>
<Header />
<LoadingCentered />
</>
);

const HouseholdPageLayout = () => {
const { metadata, isMetadataLoading, isMetadataError } = useMetadata();
const { policy, isPolicyLoading, isPolicyError } = usePolicy();
if (isMetadataLoading || isPolicyLoading) {
return <LoadingPage />;
}
if (isMetadataError || isPolicyError) {
return <ErrorPage />;
}

return (
<SuspenseLayout>
<HouseholdPage metadata={metadata} policy={policy} />
</SuspenseLayout>
);
};

const PolicyPageLayout = () => {
const { metadata, isMetadataLoading, isMetadataError } = useMetadata();
const { policy, isPolicyError, isPolicyLoading } = usePolicy();
const { userProfile, isUserProfileLoading, isUserProfileError } =
useUserProfile();
if (isMetadataLoading || isPolicyLoading || isUserProfileLoading) {
return <LoadingPage />;
}
if (isMetadataError || isUserProfileError || isPolicyError) {
return <ErrorPage />;
}
return (
<SuspenseLayout>
<PolicyPage
metadata={metadata}
policy={policy}
userProfile={userProfile}
/>
</SuspenseLayout>
);
};

const SuspenseLayout = ({ children }) => {
return (
<Suspense fallback={<LoadingPage />}>
<Header />
{children}
</Suspense>
);
};

const UserProfileRoute = () => {
const { countryId } = useParams();
// const countryId = extractCountryId();
const { userProfile, isUserProfileLoading, isUserProfileError } =
useUserProfile();

// If userProfile doesn't exist, render ErrorPage
if (isUserProfileError) {
return <ErrorPage />;
}
if (isUserProfileLoading) {
return <LoadingPage />;
}

if (!isUserProfileError) {
return <Navigate to={`/${countryId}/profile/${userProfile.user_id}`} />;
}
};
34 changes: 34 additions & 0 deletions src/hooks/useMetadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useQuery } from "@tanstack/react-query";
import { updateMetadata } from "../api/call";
import { extractCountryId } from "../pages/policy/output/utils";

const useMetadata = () => {
const countryId = extractCountryId();

// Update the metadata state when something happens to
// the countryId (e.g. the user changes the country).

// If we're accessing the page without a country ID,
// our router will handle redirecting to a country ID;
// this process is guaranteed, thus we will just not fetch
// in this situation
const queryResult = useQuery({
queryKey: ["metadata", countryId],
queryFn: () => updateMetadata(countryId),
onError: (e) => {
console.error(e);
},
enabled: !!countryId,
});

// Create a new object and return it
return {
isMetadataLoading: queryResult.isPending,
metadata: queryResult.data,
isMetadataError: queryResult.isError,
metadataError: queryResult.error,
isMetadataSuccess: queryResult.isSuccess,
};
};

export default useMetadata;
Loading
Loading