Skip to main content

@navios/di-react

React integration for the @navios/di dependency injection system. Provides React-specific providers, hooks, and contexts that enable seamless integration of dependency injection into React applications.

Package: @navios/di-react License: MIT Peer Dependencies: @navios/di, react (^18.0.0 || ^19.0.0)

Installation

npm install @navios/di-react @navios/di

Key Principles

  • Provider-Based - Container and scope provided via React Context
  • Hooks-First - All DI access through React hooks
  • Suspense Support - First-class React Suspense integration
  • Automatic Invalidation - Services re-fetch when invalidated
  • Request Scoping - Isolate services per component tree

Quick Start

import { Container, Injectable } from '@navios/di'
import { ContainerProvider, useSuspenseService } from '@navios/di-react'
import { Suspense } from 'react'

// Define service
@Injectable()
class UserService {
async getCurrentUser() {
return { id: '1', name: 'John' }
}
}

// Setup container
const container = new Container()

// App with provider
function App() {
return (
<ContainerProvider container={container}>
<Suspense fallback={<div>Loading...</div>}>
<UserProfile />
</Suspense>
</ContainerProvider>
)
}

// Component using service
function UserProfile() {
const userService = useSuspenseService(UserService)
const [user, setUser] = useState(null)

useEffect(() => {
userService.getCurrentUser().then(setUser)
}, [userService])

return <div>{user?.name}</div>
}

Providers

ContainerProvider

Makes the DI container available to all child components:

import { Container } from '@navios/di'
import { ContainerProvider } from '@navios/di-react'

const container = new Container()

function App() {
return (
<ContainerProvider container={container}>
<YourApp />
</ContainerProvider>
)
}

ScopeProvider

Creates isolated request scopes for dependency injection:

import { ScopeProvider } from '@navios/di-react'

function UserDashboard({ userId }: { userId: string }) {
return (
<ScopeProvider scopeId={`user-${userId}`} metadata={{ userId }}>
<UserProfile />
<UserSettings />
</ScopeProvider>
)
}

Hooks

useService

Fetches a service with loading/error states:

import { useService } from '@navios/di-react'

function UserProfile() {
const { data, isLoading, isError, refetch } = useService(UserService)

if (isLoading) return <div>Loading...</div>
if (isError) return <div>Error</div>

return <div>{data.name}</div>
}

useSuspenseService

Fetches a service using React Suspense:

import { useSuspenseService } from '@navios/di-react'

function UserProfile() {
const userService = useSuspenseService(UserService)
return <div>{userService.currentUser.name}</div>
}

// Wrap with Suspense
<Suspense fallback={<Loading />}>
<UserProfile />
</Suspense>

useOptionalService

Fetches a service without throwing if not registered:

import { useOptionalService } from '@navios/di-react'

function Analytics() {
const { data: analytics, isNotFound } = useOptionalService(AnalyticsService)

if (isNotFound) return null

return <AnalyticsTracker service={analytics} />
}

useInvalidateInstance

Returns a function to invalidate a service instance:

import { useService, useInvalidateInstance } from '@navios/di-react'

function UserProfile() {
const { data: user } = useService(UserService)
const invalidateInstance = useInvalidateInstance()

const handleRefresh = async () => {
if (user) {
await invalidateInstance(user)
// All components using UserService will re-fetch
}
}

return (
<div>
<span>{user?.name}</span>
<button onClick={handleRefresh}>Refresh</button>
</div>
)
}

useContainer

Direct access to the DI container:

import { useContainer } from '@navios/di-react'

function MyComponent() {
const container = useContainer()

const handleClick = async () => {
const service = await container.get(MyService)
service.doSomething()
}

return <button onClick={handleClick}>Do Something</button>
}

Service Invalidation

The hooks implement automatic invalidation:

// In one component
const { data: user } = useService(UserService)
const invalidateInstance = useInvalidateInstance()

// Invalidate the instance
if (user) {
await invalidateInstance(user)
}

// All components using UserService automatically re-fetch
const { data } = useService(UserService) // Gets fresh instance

Best Practices

Use Suspense for Cleaner Code

// Recommended
function UserProfile() {
const service = useSuspenseService(UserService)
return <div>{service.data}</div>
}

<Suspense fallback={<Loading />}>
<UserProfile />
</Suspense>

Memoize Token Arguments

// Good - stable reference
const args = useMemo(() => ({ userId }), [userId])
const { data } = useService(UserToken, args)

// Bad - creates new object every render
const { data } = useService(UserToken, { userId })

Use ScopeProvider for Request-Scoped Services

<ScopeProvider scopeId={`order-${orderId}`}>
<OrderDetails />
<OrderItems />
</ScopeProvider>