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

(BSR) feat(store): add valtio #7477

Closed
wants to merge 4 commits into from
Closed
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: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { LocationWrapper } from 'libs/location'
import { eventMonitoring } from 'libs/monitoring'
import { NetInfoWrapper } from 'libs/network/NetInfoWrapper'
import { OfflineModeContainer } from 'libs/network/OfflineModeContainer'
import { createCore } from 'libs/poc-valtio/core'
import { CoreProvider } from 'libs/poc-valtio/CoreProvider'
import { BatchMessaging, BatchPush } from 'libs/react-native-batch'
import { configureGoogleSignin } from 'libs/react-native-google-sso/configureGoogleSignin'
import { SafeAreaProvider } from 'libs/react-native-save-area-provider'
Expand Down Expand Up @@ -105,7 +107,9 @@ const App: FunctionComponent = function () {
<OnboardingWrapper>
<OfflineModeContainer>
<ScreenErrorProvider>
<AppNavigationContainer />
<CoreProvider coreInstance={createCore()}>
<AppNavigationContainer />
</CoreProvider>
</ScreenErrorProvider>
</OfflineModeContainer>
</OnboardingWrapper>
Expand Down
60 changes: 60 additions & 0 deletions src/libs/poc-valtio/CoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { Context, ReactNode, createContext, useContext, useEffect } from 'react'

import { createCore } from 'libs/poc-valtio/core'

const AppStateContext = createContext<Record<string, unknown> | null>(null)

interface ProviderProps {
children: ReactNode
coreInstance: Record<string, unknown>
}

export const CoreProvider: React.FC<ProviderProps> = ({ children, coreInstance }) => (
<AppStateContext.Provider value={coreInstance}>{children}</AppStateContext.Provider>
)

export function useAppContext<T>(): T {
const context = useContext<T | null>(AppStateContext as Context<T | null>)
if (!context) {
throw new Error('useAppContext must be used within a CortexProvider')
}
return context
}

function generateTypedHooks<
T extends Record<string, Record<string, VoidFunction> & { store: VoidFunction; getState: any }>,
>() {
// Record<string, VoidFunction>> doit inclure le store et le state
const useStore = <U extends keyof T>(service: U): ReturnType<T[U]['store']> => {
const instance = useAppContext<T>()
const serviceInstance = instance[service]
if (!serviceInstance) {
throw new Error(`Service ${String(service)} not found`)
}
return serviceInstance.store() as ReturnType<T[U]['store']>
}

const useAction = <U extends keyof T>(service: U): T[U]['actions'] => {
const instance = useAppContext<T>()
const serviceInstance = instance[service]
if (!serviceInstance) {
throw new Error(`Service ${String(service)} not found`)
}
return serviceInstance.actions
}

return { useStore, useAction }
}

const { useStore, useAction } = generateTypedHooks<ReturnType<typeof createCore>>()

export const useActivationDate = () => {
const creditStore = useStore('credit')
const { incrementCredits } = useAction('credit')

useEffect(() => {
incrementCredits()
}, [incrementCredits])

return creditStore.activationDate
}
37 changes: 37 additions & 0 deletions src/libs/poc-valtio/core.native.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createCore } from 'libs/poc-valtio/core'
import { act, renderHook } from 'tests/utils'

describe('core', () => {
it('should be instantiated with no error', () => {
const core = createCore()

expect(core).toBeDefined()
})

it('should modify the store through an action call', () => {
const core = createCore()

expect(core.user.getState().isLoggedIn).toBeFalsy()

core.user.login()

expect(core.user.getState().isLoggedIn).toBeTruthy()
})

it('should render a hook', async () => {
const core = createCore()

renderHook(() => core.user.useMyHook())

await act(async () => {})

expect(core.user.getState().firstname).toBe('xavier')
})

it('should call another service action', () => {
const core = createCore()
core.user.callsCreditService()

expect(core.credit.getState().currentCredit).toBe(1)
})
})
23 changes: 23 additions & 0 deletions src/libs/poc-valtio/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { serviceRegistry } from './serviceRegistry'
import { services } from './services/_services'

type ServicesMap = typeof services
type ServiceName = keyof ServicesMap
type ServicesType = { [K in ServiceName]: ReturnType<ServicesMap[K]> }

export function createCore() {
Object.entries(services).forEach(([name, factory]) => {
if (!serviceRegistry.hasFactory(name as ServiceName)) {
serviceRegistry.register(name as ServiceName, factory)
}
})

const allServices = serviceRegistry.getAllServices()
const core = {} as { [K in ServiceName]: unknown }

for (const [name, service] of allServices.entries()) {
core[name as ServiceName] = service
}

return core as ServicesType
}
56 changes: 56 additions & 0 deletions src/libs/poc-valtio/serviceRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { services } from 'libs/poc-valtio/services/_services'

type ServicesMap = typeof services
type ServiceName = keyof ServicesMap

type ServiceFactory<T extends ServiceName> = ServicesMap[T]

type Service<T extends ServiceName = ServiceName> = ReturnType<ServiceFactory<T>>

export class ServiceRegistry {
private static instance: ServiceRegistry
private services: Map<ServiceName, Service> = new Map()
private factories: Map<ServiceName, ServiceFactory<ServiceName>> = new Map()

static getInstance(): ServiceRegistry {
if (!ServiceRegistry.instance) {
ServiceRegistry.instance = new ServiceRegistry()
}
return ServiceRegistry.instance
}

register<T extends ServiceName>(name: T, serviceFactory: ServiceFactory<T>): void {
this.factories.set(name, serviceFactory)
}

hasFactory(name: ServiceName): boolean {
return this.factories.has(name)
}

get<T extends ServiceName>(name: T): Service<T> {
let service = this.services.get(name)

if (!service) {
const factory = this.factories.get(name)
if (!factory) {
throw new Error(`Service ${name} not found in registry`)
}
service = factory(this.get.bind(this))
this.services.set(name, service)
}

return service as Service<T>
}

getAllServices(): Map<ServiceName, Service> {
// Initialise tous les services qui ne sont pas encore créés
for (const [name] of this.factories) {
if (!this.services.has(name)) {
this.get(name)
}
}
return this.services
}
}

export const serviceRegistry = ServiceRegistry.getInstance()
7 changes: 7 additions & 0 deletions src/libs/poc-valtio/services/_services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { creditService } from 'libs/poc-valtio/services/credit.service'
import { userService } from 'libs/poc-valtio/services/user.service'

export const services = {
credit: creditService,
user: userService,
}
26 changes: 26 additions & 0 deletions src/libs/poc-valtio/services/credit.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// eslint-disable-next-line no-restricted-imports
import { create } from 'zustand'

import { serviceRegistry } from 'libs/poc-valtio/serviceRegistry'
import { useAvailableCredit } from 'shared/user/useAvailableCredit'

type CreditState = {
activationDate: Date | undefined
currentCredit: number
}

export const creditService = (_getService: typeof serviceRegistry.get) => {
const initialState: CreditState = { activationDate: undefined, currentCredit: 0 }
const store = create<CreditState>(() => initialState)

return {
store,
getState: store.getState,
useFetchCredits: () => {
useAvailableCredit()
},
incrementCredits: () => {
store.setState((state) => ({ ...state, currentCredit: state.currentCredit + 1 }))
},
}
}
41 changes: 41 additions & 0 deletions src/libs/poc-valtio/services/user.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect } from 'react'
// eslint-disable-next-line no-restricted-imports
import { create } from 'zustand'

import { serviceRegistry } from 'libs/poc-valtio/serviceRegistry'

type UserState = {
firstname: string
lastname: string
birthdate: string | null
isLoggedIn: boolean
}

export const userService = (getService: typeof serviceRegistry.get) => {
const initialState: UserState = {
firstname: '',
lastname: '',
birthdate: null,
isLoggedIn: false,
}
const store = create<UserState>(() => initialState)

return {
store,
getState: store.getState,
login: async () => {
store.setState({ isLoggedIn: true })
},
logout: () => {
store.setState({ isLoggedIn: false })
},
callsCreditService: () => {
getService('credit').incrementCredits()
},
useMyHook: () => {
useEffect(() => {
store.setState({ firstname: 'xavier' })
}, [])
},
}
}
Loading
Loading