# UseContext
*The `UseContext` and `CreateContext` [hooks](../01_RulesOfHooks.md) enable component-level context management, allowing you to share data and services within a component tree without prop drilling.*
## Overview
Context [hooks](../01_RulesOfHooks.md) provide a way to share data and services across a component tree:
- **Component Scoping** - Context values are scoped to the component and its children
- **Avoid Prop Drilling** - Share data without passing props through every level
- **Hierarchical Resolution** - Context values can be resolved from parent components
- **Lifecycle Management** - Context values are automatically disposed when the component is disposed
## Basic Usage
Use `CreateContext` to create a context value and `UseContext` to retrieve it:
```csharp
public record AppSettings(string Theme, int FontSize);
public class SettingsProvider : ViewBase
{
public override object? Build()
{
CreateContext(() => new AppSettings("dark", 14));
return new SettingsConsumer();
}
}
public class SettingsConsumer : ViewBase
{
public override object? Build()
{
var settings = UseContext<AppSettings>();
return Text.Block($"Theme: {settings.Theme}, Size: {settings.FontSize}px");
}
}
```
> **Tip:** Context is different from [services](./11_UseService.md). Services are registered globally in your application, while context is scoped to a specific component and its children. Use context for component-specific data and services for application-wide functionality.
## How Context Works
```mermaid
sequenceDiagram
participant C as Child Component
participant CC as Current Context
participant PC as Parent Context
participant AC as Ancestor Context
Note over C,AC: Child calls UseContext<T>()
C->>CC: Check current context
CC-->>C: Not found
C->>PC: Check parent context
PC-->>C: Not found
C->>AC: Check ancestor context
AC-->>C: Found! Return value
```
### Context Scoping
Context values are scoped to the component where they are created:
```csharp
public class AppUserContext
{
public string UserId { get; set; } = "";
}
public class SectionConfig
{
public string Title { get; set; } = "";
}
public class AppView : ViewBase
{
public override object? Build()
{
// Context created here - available to all children
CreateContext(() => new AppUserContext { UserId = "123" });
return Layout.Vertical()
| new SectionView() // Can access userContext
| new AnotherView(); // Can also access userContext
}
}
public class SectionView : ViewBase
{
public override object? Build()
{
var user = UseContext<AppUserContext>(); // Works - found in parent
// Create a new context for this section's children
CreateContext(() => new SectionConfig { Title = "Settings" });
return new NestedView(); // Can access both userContext and sectionConfig
}
}
public class NestedView : ViewBase
{
public override object? Build()
{
var user = UseContext<AppUserContext>(); // Works - found in ancestor
var config = UseContext<SectionConfig>(); // Works - found in parent
return Text.P($"{config.Title} for User {user.UserId}");
}
}
public class AnotherView : ViewBase
{
public override object? Build()
{
var user = UseContext<AppUserContext>(); // Works - found in ancestor
return Text.P($"Another view - User: {user.UserId}");
}
}
```
## When to Use Context
| Use Context For | Use Services Instead For |
|-----------------|--------------------------|
| Component-Specific Configuration | Application-Wide Services |
| Shared State (avoid prop drilling) | Singleton Services |
| Component-Scoped Services | Infrastructure Services (logging, database, HTTP) |
| Theme and Styling | |
| Feature Flags (component tree specific) | |
## Lifecycle Management
Context values that implement `IDisposable` are automatically disposed when the component is disposed:
```csharp
public class DisposableResource : IDisposable
{
public string ResourceId { get; set; } = "";
public bool IsDisposed { get; private set; }
public DisposableResource(string id)
{
ResourceId = id;
}
public void Dispose()
{
IsDisposed = true;
}
}
public class ResourceView : ViewBase
{
public override object? Build()
{
// ResourceContext will be automatically disposed when ResourceView is disposed
CreateContext(() => new DisposableResource("resource-123"));
var resource = UseContext<DisposableResource>();
return Layout.Vertical()
| Text.P($"Resource ID: {resource.ResourceId}")
| Text.P($"Disposed: {resource.IsDisposed}")
| new ResourceConsumer();
}
}
public class ResourceConsumer : ViewBase
{
public override object? Build()
{
var resource = UseContext<DisposableResource>();
return Text.P($"Using resource: {resource.ResourceId}");
}
}
```
## Common Patterns
### Provider Component
Create a provider component that sets up context for its children:
```csharp
public class ThemeContext
{
public string PrimaryColor { get; set; } = "blue";
public string SecondaryColor { get; set; } = "gray";
}
public class ThemeProvider : ViewBase
{
public override object? Build()
{
// Create theme context for children
CreateContext(() => new ThemeContext
{
PrimaryColor = "blue",
SecondaryColor = "gray"
});
return new ThemedContent();
}
}
public class ThemedContent : ViewBase
{
public override object? Build()
{
var theme = UseContext<ThemeContext>();
return Layout.Vertical()
| Text.P($"Primary Color: {theme.PrimaryColor}")
| Text.P($"Secondary Color: {theme.SecondaryColor}");
}
}
```
### Context with Factory
Use factory functions for lazy initialization:
```csharp
public class MemoryCache
{
public int ItemCount { get; set; }
public bool IsInitialized { get; set; }
public void Initialize()
{
IsInitialized = true;
ItemCount = 0;
}
}
public class DataView : ViewBase
{
public override object? Build()
{
// Context is only created when first accessed
CreateContext(() =>
{
var c = new MemoryCache();
c.Initialize();
return c;
});
return new DataListView();
}
}
public class DataListView : ViewBase
{
public override object? Build()
{
var cache = UseContext<MemoryCache>();
return Layout.Vertical()
| Text.P($"Cache initialized: {cache.IsInitialized}")
| Text.P($"Items in cache: {cache.ItemCount}");
}
}
```
### Conditional Context
Create context conditionally based on state:
```csharp
public class AuthUserContext
{
public string UserId { get; set; } = "";
public string UserName { get; set; } = "";
}
public class ConditionalView : ViewBase
{
public override object? Build()
{
var isAuthenticated = UseState(false);
if (isAuthenticated.Value)
{
CreateContext(() => new AuthUserContext
{
UserId = "123",
UserName = "John Doe"
});
}
return Layout.Vertical()
| new Button($"{(isAuthenticated.Value ? "Logout" : "Login")}",
onClick: _ => isAuthenticated.Set(!isAuthenticated.Value))
| (isAuthenticated.Value
? new ConditionalAuthView()
: Text.P("Please login"));
}
}
public class ConditionalAuthView : ViewBase
{
public override object? Build()
{
var user = UseContext<AuthUserContext>();
return Layout.Vertical()
| Text.P($"Welcome, {user.UserName}!")
| Text.P($"User ID: {user.UserId}");
}
}
```
## Best Practices
- **Use context for component-scoped data** - Use services for app-wide data
- **Keep context values simple** - Data containers or lightweight services; use DI for heavy services
- **Use type safety** - Always use `UseContext<T>()` instead of runtime type checking
- **Avoid frequently changing data** - Use [state](./03_UseState.md) for reactive updates
- **Document context dependencies** - Make it clear when a component requires a parent context
## See Also
- [Services](./11_UseService.md) - Application-wide dependency injection
- [State](./03_UseState.md) - Reactive state management
- [Rules of Hooks](../02_RulesOfHooks.md) - Understanding hook rules and best practices
- [Views](../../../01_Onboarding/02_Concepts/02_Views.md) - Understanding Ivy views and components
## Examples
### User Context
```csharp
public class UserContext
{
public string UserName { get; set; } = "";
}
public class AuthenticatedView : ViewBase
{
public override object? Build()
{
CreateContext(() => new UserContext { UserName = "John Doe" });
return Layout.Vertical()
| new UserProfileView()
| new UserSettingsView();
}
}
public class UserProfileView : ViewBase
{
public override object? Build()
{
var user = UseContext<UserContext>();
return Text.P($"Welcome, {user.UserName}!");
}
}
public class UserSettingsView : ViewBase
{
public override object? Build()
{
var user = UseContext<UserContext>();
return Text.P($"Settings for {user.UserName}");
}
}
```
### Component-Scoped Service
```csharp
public class ScopedMemoryCache : IDisposable
{
private readonly Dictionary<string, string> _cache = new();
public void Set(string key, string value) => _cache[key] = value;
public string? Get(string key) => _cache.TryGetValue(key, out var value) ? value : null;
public void Clear() => _cache.Clear();
public int Count => _cache.Count;
public void Dispose() => _cache.Clear();
}
public class CacheDataView : ViewBase
{
public override object? Build()
{
CreateContext(() => new ScopedMemoryCache());
var refreshState = UseState(0);
CreateContext(() => refreshState);
return Layout.Vertical()
| new CacheDataListView()
| new CacheDataDetailView();
}
}
public class CacheDataListView : ViewBase
{
public override object? Build()
{
var cache = UseContext<ScopedMemoryCache>();
var refreshState = UseContext<IState<int>>();
var _ = refreshState.Value;
var data = cache.Get("data") ?? "No data";
return Layout.Vertical()
| Text.P($"Cached: {data}")
| Text.P($"Entries: {cache.Count}")
| new Button("Set Data", onClick: _ =>
{
cache.Set("data", "Sample Data");
refreshState.Set(refreshState.Value + 1);
});
}
}
public class CacheDataDetailView : ViewBase
{
public override object? Build()
{
var cache = UseContext<ScopedMemoryCache>();
var refreshState = UseContext<IState<int>>();
var _ = refreshState.Value;
return Layout.Vertical()
| Text.P($"Cache entries: {cache.Count}")
| new Button("Clear Cache", onClick: _ =>
{
cache.Clear();
refreshState.Set(refreshState.Value + 1);
});
}
}
```