diff --git a/README.md b/README.md index e52aaa1a..5bb8f2fa 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The goal of this project is to provide a React with TypeScript starter applicati - Platform: [React](https://react.dev/) with [TypeScript](https://www.typescriptlang.org/) - Component Library: [Comet Component Library](https://github.com/MetroStar/comet) - Data Visualization: [Victory Charts](https://formidable.com/open-source/victory/) -- State Management: [Recoil](https://recoiljs.org/) +- State Management: [Jotai](https://jotai.org/) - Form Validation: [React Hook Form](https://react-hook-form.com/) - Unit Testing: [Vitest](https://vitest.dev/) with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) - Code Analysis: [ESLint](https://eslint.org/) diff --git a/package-lock.json b/package-lock.json index 1c8d63d8..9589a70c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,13 +17,13 @@ "@uswds/uswds": "3.10.0", "axios": "1.7.9", "axios-mock-adapter": "2.1.0", + "jotai": "2.10.4", "oidc-client-ts": "^3.1.0", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "7.54.1", "react-oidc-context": "^3.2.0", - "react-router-dom": "7.0.2", - "recoil": "0.7.7" + "react-router-dom": "7.0.2" }, "devDependencies": { "@eslint/js": "^9.17.0", @@ -2757,13 +2757,13 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.17.tgz", "integrity": "sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2792,7 +2792,7 @@ "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true + "devOptional": true }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", @@ -5472,7 +5472,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/cypress": { "version": "13.16.1", @@ -7809,11 +7809,6 @@ "unenv": "^1.10.0" } }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -8956,6 +8951,27 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jotai": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.4.tgz", + "integrity": "sha512-/T4ofyMSkAybEs2OvR8S4HACa+/ASUEPLz86SUjFXJqU9RdJKLvZDJrag398suvHC5CR0+Cs4P5m/gtVcryzlw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/jpeg-js": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", @@ -12131,25 +12147,6 @@ "object-assign": "^4.1.0" } }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", diff --git a/package.json b/package.json index d1a9ab80..4b627df3 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,13 @@ "@uswds/uswds": "3.10.0", "axios": "1.7.9", "axios-mock-adapter": "2.1.0", + "jotai": "2.10.4", "oidc-client-ts": "^3.1.0", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "7.54.1", "react-oidc-context": "^3.2.0", - "react-router-dom": "7.0.2", - "recoil": "0.7.7" + "react-router-dom": "7.0.2" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/components/header/header.test.tsx b/src/components/header/header.test.tsx index a88b47d1..dd5cf968 100644 --- a/src/components/header/header.test.tsx +++ b/src/components/header/header.test.tsx @@ -2,8 +2,8 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { User } from '../../types/user'; import { Header } from './header'; @@ -11,11 +11,11 @@ import { Header } from './header'; describe('Header', () => { const headerComponent = ( - +
- + ); diff --git a/src/components/protected-route/protected-route.test.tsx b/src/components/protected-route/protected-route.test.tsx index 4bf8786a..be74dd48 100644 --- a/src/components/protected-route/protected-route.test.tsx +++ b/src/components/protected-route/protected-route.test.tsx @@ -1,19 +1,19 @@ import { User } from '@src/types/user'; import { act, render } from '@testing-library/react'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { ProtectedRoute } from './protected-route'; describe('ProtectedRoute', () => { const wrapperComponent = ( - + - + ); diff --git a/src/hooks/use-auth-sso.test.tsx b/src/hooks/use-auth-sso.test.tsx index 9dfee306..b17e9ac3 100644 --- a/src/hooks/use-auth-sso.test.tsx +++ b/src/hooks/use-auth-sso.test.tsx @@ -1,5 +1,5 @@ import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; +import { Provider } from 'jotai'; import useAuth from './use-auth'; // Import your useAuth function interface ContextWrapperProps { @@ -24,7 +24,7 @@ describe('useAuth', () => { }); const contextWrapper = ({ children }: ContextWrapperProps) => ( - {children} + {children} ); it('should set isSignedIn to true when authenticated with sso', async () => { diff --git a/src/hooks/use-auth.test.tsx b/src/hooks/use-auth.test.tsx index bb499fe6..a35b0668 100644 --- a/src/hooks/use-auth.test.tsx +++ b/src/hooks/use-auth.test.tsx @@ -1,7 +1,7 @@ import keycloak from '@src/utils/keycloak'; import { act, renderHook } from '@testing-library/react'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; -import { RecoilRoot } from 'recoil'; import useAuth from './use-auth'; interface ContextWrapperProps { @@ -16,7 +16,7 @@ describe('useAuth', () => { const contextWrapper = ({ children }: ContextWrapperProps) => ( - {children} + {children} ); diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts index 261b87e4..a8d841ec 100644 --- a/src/hooks/use-auth.ts +++ b/src/hooks/use-auth.ts @@ -1,19 +1,19 @@ import { getSignInRedirectUrl } from '@src/utils/auth'; +import { useAtom } from 'jotai'; import { useEffect, useState } from 'react'; import { useAuth as useKeycloakAuth } from 'react-oidc-context'; -import { useRecoilState } from 'recoil'; import { userData } from '../data/user'; import { currentUserState, signedInState } from '../store'; import { User } from '../types/user'; const useAuth = () => { const auth = useKeycloakAuth(); - const [isSignedIn, setIsSignedIn] = useRecoilState(signedInState); + const [isSignedIn, setIsSignedIn] = useAtom(signedInState); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); - const [currentUserData, setCurrentUserData] = useRecoilState< - User | undefined - >(currentUserState); + const [currentUserData, setCurrentUserData] = useAtom( + currentUserState, + ); /* TODO: Uncomment for interacting with own API, no need to send tokens to external public API */ // useEffect(() => { diff --git a/src/main.tsx b/src/main.tsx index c6d8f2a0..0de44337 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,8 +1,8 @@ +import { Provider } from 'jotai'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { AuthProvider } from 'react-oidc-context'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import { App } from './App.tsx'; import './styles.scss'; import keycloak from './utils/keycloak.ts'; @@ -11,9 +11,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - + - + , diff --git a/src/pages/dashboard/dashboard-bar-chart/dashboard-bar-chart.test.tsx b/src/pages/dashboard/dashboard-bar-chart/dashboard-bar-chart.test.tsx index dd76c878..bd0d24b2 100644 --- a/src/pages/dashboard/dashboard-bar-chart/dashboard-bar-chart.test.tsx +++ b/src/pages/dashboard/dashboard-bar-chart/dashboard-bar-chart.test.tsx @@ -1,17 +1,17 @@ import { mockData } from '@src/data/spacecraft'; import { act, render } from '@testing-library/react'; +import { Provider } from 'jotai'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import { DashboardBarChart } from './dashboard-bar-chart'; describe('DashboardBarChart', () => { test('should render successfully', async () => { const { baseElement } = render( - + - , + , ); await act(async () => { expect(baseElement).toBeTruthy(); diff --git a/src/pages/dashboard/dashboard-pie-chart/dashboard-pie-chart.test.tsx b/src/pages/dashboard/dashboard-pie-chart/dashboard-pie-chart.test.tsx index 54e12ac6..f521d10f 100644 --- a/src/pages/dashboard/dashboard-pie-chart/dashboard-pie-chart.test.tsx +++ b/src/pages/dashboard/dashboard-pie-chart/dashboard-pie-chart.test.tsx @@ -1,17 +1,17 @@ import { mockData } from '@src/data/spacecraft'; import { act, render } from '@testing-library/react'; +import { Provider } from 'jotai'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import { DashboardPieChart } from './dashboard-pie-chart'; describe('DashboardPieChart', () => { test('should render successfully', async () => { const { baseElement } = render( - + - , + , ); await act(async () => { expect(baseElement).toBeTruthy(); diff --git a/src/pages/dashboard/dashboard-table/dashboard-table.test.tsx b/src/pages/dashboard/dashboard-table/dashboard-table.test.tsx index 9b40e20e..d277f1de 100644 --- a/src/pages/dashboard/dashboard-table/dashboard-table.test.tsx +++ b/src/pages/dashboard/dashboard-table/dashboard-table.test.tsx @@ -1,28 +1,28 @@ import { mockData } from '@src/data/spacecraft'; import { act, render } from '@testing-library/react'; +import { Provider } from 'jotai'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import { DashboardTable } from './dashboard-table'; describe('DashboardTable', () => { test('should render successfully', () => { const { baseElement } = render( - + - , + , ); expect(baseElement).toBeTruthy(); }); test('should render with mock data', async () => { const { baseElement } = render( - + - , + , ); await act(async () => { expect(baseElement).toBeTruthy(); diff --git a/src/pages/dashboard/dashboard.test.tsx b/src/pages/dashboard/dashboard.test.tsx index 5c3ad9dd..d9a6109e 100644 --- a/src/pages/dashboard/dashboard.test.tsx +++ b/src/pages/dashboard/dashboard.test.tsx @@ -3,9 +3,9 @@ import axios from '@src/utils/axios'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { act, render } from '@testing-library/react'; import MockAdapter from 'axios-mock-adapter'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { User } from '../../types/user'; import { Dashboard } from './dashboard'; @@ -20,13 +20,13 @@ describe('Dashboard', () => { }); const componentWrapper = ( - + - + ); diff --git a/src/pages/details/details.test.tsx b/src/pages/details/details.test.tsx index 62c6e8b2..266d446a 100644 --- a/src/pages/details/details.test.tsx +++ b/src/pages/details/details.test.tsx @@ -3,9 +3,9 @@ import axios from '@src/utils/axios'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, waitFor } from '@testing-library/react'; import MockAdapter from 'axios-mock-adapter'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { User } from '../../types/user'; import { Details } from './details'; @@ -30,13 +30,13 @@ describe('Details', () => { }); const componentWrapper = ( - +
- + ); diff --git a/src/pages/home/home.test.tsx b/src/pages/home/home.test.tsx index 0ec19a13..1bc41da4 100644 --- a/src/pages/home/home.test.tsx +++ b/src/pages/home/home.test.tsx @@ -1,7 +1,7 @@ import { act, render } from '@testing-library/react'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { User } from '../../types/user'; import { Home } from './home'; @@ -9,11 +9,11 @@ import { Home } from './home'; describe('Home', () => { const componentWrapper = ( - + - + ); diff --git a/src/pages/search-results/search-results.test.tsx b/src/pages/search-results/search-results.test.tsx index 5282a109..9120f988 100644 --- a/src/pages/search-results/search-results.test.tsx +++ b/src/pages/search-results/search-results.test.tsx @@ -4,9 +4,9 @@ import axios from '@src/utils/axios'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { act, render } from '@testing-library/react'; import MockAdapter from 'axios-mock-adapter'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; import { BrowserRouter } from 'react-router-dom'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { SearchResults } from './search-results'; @@ -21,13 +21,13 @@ describe('SearchResults', () => { const componentWrapper = ( - + - + ); diff --git a/src/pages/sign-in/sign-in.test.tsx b/src/pages/sign-in/sign-in.test.tsx index 882185f6..2f538445 100644 --- a/src/pages/sign-in/sign-in.test.tsx +++ b/src/pages/sign-in/sign-in.test.tsx @@ -2,8 +2,8 @@ import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'jotai'; import { AuthProvider } from 'react-oidc-context'; -import { RecoilRoot } from 'recoil'; import * as useAuthMock from '../../hooks/use-auth'; import { User } from '../../types/user'; import { SignIn } from './sign-in'; @@ -11,11 +11,11 @@ import { SignIn } from './sign-in'; describe('SignIn', () => { const signInComponent = ( - + - + ); diff --git a/src/store.ts b/src/store.ts index 7ed2ce15..f224778f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,14 +1,7 @@ import { User } from '@src/types/user'; -import { atom } from 'recoil'; +import { atom } from 'jotai'; -const signedInState = atom({ - key: 'signedIn', - default: false, -}); - -const currentUserState = atom({ - key: 'currentUser', - default: undefined, -}); +const signedInState = atom(false); +const currentUserState = atom(undefined); export { currentUserState, signedInState };