# Providers
React context providers manage service lifecycle and provide shared state.
## Provider Chain
The providers wrap the application in a specific order:
```typescript
// App.tsx
```
**Order matters because:**
1. Redux must be outermost for state access
2. PersistGate rehydrates state before rendering children
3. SocketProvider depends on Redux auth token
4. TelegramProvider depends on Redux telegram state
5. HashRouter provides navigation to all routes
## SocketProvider (`providers/SocketProvider.tsx`)
Manages Socket.io connection lifecycle and MCP initialization.
### Responsibilities
- Auto-connect when auth token is available
- Auto-disconnect when token is cleared
- Initialize MCP server when socket connects
- Update Redux with connection status
### Implementation
```typescript
interface SocketContextValue {
socket: Socket | null;
isConnected: boolean;
emit: (event: string, data: unknown) => void;
on: (event: string, handler: Function) => void;
off: (event: string, handler: Function) => void;
}
export function SocketProvider({ children }) {
const token = useAppSelector((state) => state.auth.token);
const userId = useAppSelector((state) => state.user.profile?.id);
const dispatch = useAppDispatch();
useEffect(() => {
if (!token || !userId) {
socketService.disconnect();
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
return;
}
// Connect with auth token
socketService.connect(token);
dispatch(setSocketStatus({ userId, status: 'connecting' }));
// Handle connection events
socketService.on('connect', () => {
dispatch(setSocketStatus({ userId, status: 'connected' }));
dispatch(setSocketId({ userId, socketId: socketService.getSocket()?.id }));
// Initialize MCP server
initMCPServer(socketService.getSocket());
});
socketService.on('disconnect', () => {
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
cleanupMCP();
});
socketService.on('connect_error', (error) => {
console.error('Socket connection error:', error);
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
});
return () => {
socketService.disconnect();
cleanupMCP();
};
}, [token, userId]);
const contextValue: SocketContextValue = {
socket: socketService.getSocket(),
isConnected: socketService.isConnected(),
emit: socketService.emit.bind(socketService),
on: socketService.on.bind(socketService),
off: socketService.off.bind(socketService)
};
return (
{children}
);
}
```
### Usage
```typescript
import { useSocket } from '../providers/SocketProvider';
function MyComponent() {
const { socket, isConnected, emit, on, off } = useSocket();
useEffect(() => {
const handler = (data) => console.log('Received:', data);
on('event-name', handler);
return () => off('event-name', handler);
}, [on, off]);
const sendMessage = () => {
emit('send-message', { text: 'Hello!' });
};
return (
Status: {isConnected ? 'Connected' : 'Disconnected'}
);
}
```
## TelegramProvider (`providers/TelegramProvider.tsx`)
Manages Telegram MTProto connection lifecycle.
### Responsibilities
- Initialize MTProto client when user is authenticated
- Connect to Telegram servers
- Store session string in Redux
- Provide Telegram context to children
### Implementation
```typescript
interface TelegramContextValue {
client: TelegramClient | null;
connectionStatus: ConnectionStatus;
authStatus: AuthStatus;
connect: () => Promise;
disconnect: () => Promise;
}
export function TelegramProvider({ children }) {
const dispatch = useAppDispatch();
const userId = useAppSelector((state) => state.user.profile?.id);
const telegramState = useAppSelector((state) =>
state.telegram.byUser[userId]
);
useEffect(() => {
if (!userId) return;
// Parallel initialization for faster startup
const init = async () => {
try {
// Initialize and connect in parallel
await Promise.all([
dispatch(initializeTelegram(userId)).unwrap(),
dispatch(connectTelegram(userId)).unwrap()
]);
} catch (error) {
console.error('Telegram initialization failed:', error);
}
};
init();
return () => {
dispatch(disconnectTelegram(userId));
};
}, [userId]);
// Restore session from persisted state
useEffect(() => {
if (telegramState?.sessionString) {
const client = mtprotoService.getInstance().getClient();
if (client) {
client.setSession(telegramState.sessionString);
}
}
}, [telegramState?.sessionString]);
const contextValue: TelegramContextValue = {
client: mtprotoService.getInstance().getClient(),
connectionStatus: telegramState?.connectionStatus || 'disconnected',
authStatus: telegramState?.authStatus || 'not_authenticated',
connect: () => dispatch(connectTelegram(userId)).unwrap(),
disconnect: () => dispatch(disconnectTelegram(userId)).unwrap()
};
return (
{children}
);
}
```
### Usage
```typescript
import { useTelegram } from '../providers/TelegramProvider';
function ChatList() {
const { client, connectionStatus, authStatus } = useTelegram();
const [chats, setChats] = useState([]);
useEffect(() => {
if (connectionStatus === 'connected' && authStatus === 'authenticated') {
const fetchChats = async () => {
const dialogs = await client.getDialogs({ limit: 20 });
setChats(dialogs);
};
fetchChats();
}
}, [client, connectionStatus, authStatus]);
if (connectionStatus !== 'connected') {
return Connecting to Telegram...
;
}
return (
{chats.map((chat) => (
- {chat.title}
))}
);
}
```
## UserProvider (`providers/UserProvider.tsx`)
Minimal user context provider (most user state is in Redux).
### Responsibilities
- Legacy user context for compatibility
- May be deprecated in favor of Redux
### Implementation
```typescript
interface UserContextValue {
user: User | null;
loading: boolean;
}
export function UserProvider({ children }) {
const user = useAppSelector((state) => state.user.profile);
const loading = useAppSelector((state) => state.user.loading);
return (
{children}
);
}
```
### Usage
```typescript
import { useUserContext } from '../providers/UserProvider';
function Header() {
const { user, loading } = useUserContext();
if (loading) return ;
if (!user) return null;
return Welcome, {user.firstName};
}
```
## Provider Patterns
### Effect-Based Lifecycle
Providers use `useEffect` to manage service lifecycle:
```typescript
useEffect(() => {
// Setup on mount or dependency change
service.connect();
// Cleanup on unmount or dependency change
return () => {
service.disconnect();
};
}, [dependencies]);
```
### Redux Integration
Providers read from and dispatch to Redux:
```typescript
// Read state
const token = useAppSelector(state => state.auth.token);
// Dispatch actions
const dispatch = useAppDispatch();
dispatch(setStatus({ userId, status: 'connected' }));
```
### Parallel Initialization
TelegramProvider runs init and connect in parallel:
```typescript
await Promise.all([
dispatch(initializeTelegram(userId)).unwrap(),
dispatch(connectTelegram(userId)).unwrap(),
]);
```
This reduces startup time compared to sequential operations.
### Session Restoration
Providers restore persisted state on mount:
```typescript
useEffect(() => {
if (persistedSession) {
service.restoreSession(persistedSession);
}
}, [persistedSession]);
```
## Context vs Redux
| Use Context For | Use Redux For |
| ---------------------------------- | ---------------------------------- |
| Service instances (socket, client) | Serializable state (status, data) |
| Methods (emit, on, off) | Persisted state (sessions, tokens) |
| Derived values | Complex state logic |
Example:
- `SocketContext` provides `socket` instance and `emit` method
- Redux stores `socketStatus` and `socketId`
## Testing Providers
### Mock Provider for Tests
```typescript
// test-utils.tsx
const mockSocketContext: SocketContextValue = {
socket: null,
isConnected: true,
emit: jest.fn(),
on: jest.fn(),
off: jest.fn()
};
export function TestProviders({ children }) {
return (
{children}
);
}
```
### Testing Provider Effects
```typescript
test('SocketProvider connects when token is available', () => {
const store = createTestStore({ auth: { token: 'test-token' } });
render(
);
expect(socketService.connect).toHaveBeenCalledWith('test-token');
});
```
---
_Previous: [Components](./06-components.md) | Next: [Hooks & Utils](./08-hooks-utils.md)_