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

chore: creates persisted store for Active Project Id using Zustand #67

Merged
merged 13 commits into from
Dec 19, 2024
33 changes: 32 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
"typescript-eslint": "^8.15.0",
"vite": "^5.4.11",
"vite-plugin-svgr": "^4.3.0",
"vitest": "2.1.8"
"vitest": "2.1.8",
"zustand": "5.0.2"
},
"overrides": {
"better-sqlite3": "11.5.0"
Expand Down
41 changes: 41 additions & 0 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CssBaseline, ThemeProvider } from '@mui/material'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'

import { theme } from './Theme'
import {
ActiveProjectIdProvider,
CreateActiveProjectIdStore,
} from './contexts/ActiveProjectIdProvider'
import { ApiProvider } from './contexts/ApiContext'
import { IntlProvider } from './contexts/IntlContext'
import { routeTree } from './routeTree.gen'

const queryClient = new QueryClient()

const router = createRouter({ routeTree })

declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

const PersistedProjectIdStore = CreateActiveProjectIdStore({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const PersistedProjectIdStore = CreateActiveProjectIdStore({
const persistedProjectIdStore = createActiveProjectIdStore({

I think my comment before was maybe not very clear, I did not mean for you to change the case of CreateACtiveProjectIdStore

persist: true,
})

export const App = () => (
<ThemeProvider theme={theme}>
<CssBaseline />
<IntlProvider>
<QueryClientProvider client={queryClient}>
<ActiveProjectIdProvider store={PersistedProjectIdStore}>
<ApiProvider>
<RouterProvider router={router} />
</ApiProvider>
</ActiveProjectIdProvider>
</QueryClientProvider>
</IntlProvider>
</ThemeProvider>
)
61 changes: 61 additions & 0 deletions src/renderer/src/contexts/ActiveProjectIdProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createContext, type ReactNode } from 'react'
import { createStore, type StoreApi } from 'zustand'
import { persist as zustandPersist } from 'zustand/middleware'

import { createHooks } from './createStoreHooks'

const PERSISTED_ACTIVE_PROJECT_ID_KEY = 'ActiveProjectId'

type ActiveProjectId = { activeProjectId: string | undefined }

const initialActiveProjectId: ActiveProjectId = {
activeProjectId: undefined,
}

type ActiveProjectIdStore = ReturnType<typeof CreateActiveProjectIdStore>

type ActiveProjectIdProviderProps = {
children: ReactNode
store: ActiveProjectIdStore
}

const ActiveProjectIdContext = createContext<ActiveProjectIdStore | null>(null)

export const ActiveProjectIdProvider = ({
children,
store,
}: ActiveProjectIdProviderProps) => {
return (
<ActiveProjectIdContext.Provider value={store}>
{children}
</ActiveProjectIdContext.Provider>
)
}

const { useStoreActions, useStoreState } = createHooks(ActiveProjectIdContext)

export {
useStoreActions as useActiveProjectIdStoreActions,
useStoreState as useActiveProjectIdStoreState,
}

export function CreateActiveProjectIdStore({ persist }: { persist: boolean }) {
let store: StoreApi<ActiveProjectId>

if (!persist) {
store = createStore(() => initialActiveProjectId)
} else {
store = createStore(
zustandPersist(() => initialActiveProjectId, {
name: PERSISTED_ACTIVE_PROJECT_ID_KEY,
}),
)
}

const actions = {
setActiveProjectId: (newProjectId: string) =>
store.setState({ activeProjectId: newProjectId }),
}

return { store, actions }
}
31 changes: 31 additions & 0 deletions src/renderer/src/contexts/createStoreHooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useContext } from 'react'
import { useStore, type StoreApi } from 'zustand'

export function createHooks<TStoreState, TStoreActions>(
context: React.Context<{
store: StoreApi<TStoreState>
actions: TStoreActions
} | null>,
) {
function useContextValue() {
const value = useContext(context)
if (!value) {
throw new Error('Must set up the provider first')
}
return value
}

function useStoreState(): TStoreState
function useStoreState<S>(selector: (state: TStoreState) => S): S
function useStoreState<S>(selector?: (state: TStoreState) => S) {
const { store } = useContextValue()
return useStore(store, selector!)
}

function useStoreActions() {
const { actions } = useContextValue()
return actions
}

return { useStoreState, useStoreActions }
}
13 changes: 2 additions & 11 deletions src/renderer/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { createRoot } from 'react-dom/client'

import { routeTree } from './routeTree.gen'

import './index.css'

const router = createRouter({ routeTree })

declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
import { App } from './App'

const root = createRoot(document.getElementById('app') as HTMLElement)

root.render(<RouterProvider router={router} />)
root.render(<App />)
5 changes: 4 additions & 1 deletion src/renderer/src/routes/Onboarding/CreateProjectScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '../../components/Onboarding/onboardingLogic'
import { Text } from '../../components/Text'
import { PROJECT_NAME_MAX_LENGTH_GRAPHEMES } from '../../constants'
import { useActiveProjectIdStoreActions } from '../../contexts/ActiveProjectIdProvider'
import { useCreateProject } from '../../hooks/mutations/projects'
import ProjectImage from '../../images/add_square.png'

Expand Down Expand Up @@ -110,6 +111,7 @@ function CreateProjectScreenComponent() {
const [error, setError] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const setProjectNameMutation = useCreateProject()
const { setActiveProjectId } = useActiveProjectIdStoreActions()

const [configFileName, setConfigFileName] = useState<string | null>(null)

Expand All @@ -134,7 +136,8 @@ function CreateProjectScreenComponent() {
return
}
setProjectNameMutation.mutate(projectName, {
onSuccess: () => {
onSuccess: (projectId) => {
setActiveProjectId(projectId)
navigate({ to: '/tab1' })
},
onError: (error) => {
Expand Down
19 changes: 16 additions & 3 deletions src/renderer/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useLayoutEffect } from 'react'
import { useOwnDeviceInfo } from '@comapeo/core-react'
import { createFileRoute, useNavigate } from '@tanstack/react-router'

import { useActiveProjectIdStoreState } from '../contexts/ActiveProjectIdProvider'

export const Route = createFileRoute('/')({
component: RouteComponent,
})
Expand All @@ -12,14 +14,25 @@ export const Route = createFileRoute('/')({
function RouteComponent() {
const navigate = useNavigate()
const { data } = useOwnDeviceInfo()
const hasCreatedDeviceName = data?.name !== undefined
const hasCreatedDeviceName = data.name !== undefined
const activeProjectId = useActiveProjectIdStoreState(
(store) => store.activeProjectId,
)

console.log({ activeProjectId })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log({ activeProjectId })


useLayoutEffect(() => {
if (!hasCreatedDeviceName) {
navigate({ to: '/Onboarding' })
} else {
navigate({ to: '/tab1' })
return
}

if (!activeProjectId) {
navigate({ to: '/Onboarding/CreateJoinProjectScreen' })
return
}

navigate({ to: '/tab1' })
})

return null
Expand Down
Loading