Skip to content

Pain Points Solutions

React development suffers from persistent pain points that compound over time. TDI2’s dependency injection approach provides measurable solutions to these challenges, often reducing complexity by 60-80%.

State Management Complexity (90% Reduction)

Section titled “State Management Complexity (90% Reduction)”

React Problem: Complex state coordination across components

// Redux boilerplate for cart functionality
const FETCH_CART_START = 'FETCH_CART_START';
const ADD_TO_CART = 'ADD_TO_CART';
const REMOVE_FROM_CART = 'REMOVE_FROM_CART';
// Actions, reducers, selectors, store setup...
// 100+ lines for basic cart operations

TDI2 Solution: Direct service interaction

@Service()
export class CartService implements CartServiceInterface {
state = {
items: [] as CartItem[],
total: 0,
loading: false
};
addItem(product: Product, quantity = 1): void {
const existingItem = this.state.items.find(item => item.productId === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
this.state.items.push({ productId: product.id, product, quantity, price: product.price });
}
this.calculateTotal();
}
private calculateTotal(): void {
this.state.total = this.state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}

Impact: 15 lines vs 100+ lines, reactive updates, no boilerplate.

React Problem: Complex test setup with multiple providers

const renderWithProviders = (component) => {
const mockStore = configureStore({
reducer: { cart: cartReducer, user: userReducer, products: productReducer }
});
return render(
<Provider store={mockStore}>
<Router>
<ThemeProvider theme={mockTheme}>
<UserContext.Provider value={mockUser}>
{component}
</UserContext.Provider>
</ThemeProvider>
</Router>
</Provider>
);
};

TDI2 Solution: Simple service mocking

test('should add product to cart', () => {
const mockCartService = {
addItem: jest.fn(),
state: { items: [], total: 0, loading: false }
};
render(<ProductCard product={mockProduct} cartService={mockCartService} />);
fireEvent.click(screen.getByText('Add to Cart'));
expect(mockCartService.addItem).toHaveBeenCalledWith(mockProduct, 1);
});

Impact: Direct service testing, no provider setup, isolated unit tests.

Tight Coupling Between Components (75% Reduction)

Section titled “Tight Coupling Between Components (75% Reduction)”

React Problem: Prop drilling through component hierarchy

function App() {
const [user, setUser] = useState(null);
const [cart, setCart] = useState({ items: [], total: 0 });
const [products, setProducts] = useState([]);
return (
<div>
<Header user={user} cart={cart} onLogout={() => setUser(null)} />
<ProductCatalog
user={user}
cart={cart}
products={products}
onAddToCart={(product) => setCart(prev => ({ ...prev, items: [...prev.items, product] }))}
/>
<CartSidebar cart={cart} onUpdateCart={setCart} />
</div>
);
}

TDI2 Solution: Automatic dependency injection

function App() {
return (
<div>
<Header />
<ProductCatalog />
<CartSidebar />
</div>
);
}
function Header({ userService, cartService }: ServicesProps) {
const { currentUser } = userService.state;
const { itemCount } = cartService.state;
return (
<header>
<span>Welcome, {currentUser?.name}</span>
<span>Cart ({itemCount})</span>
<button onClick={() => userService.logout()}>Logout</button>
</header>
);
}

Impact: Services automatically available where needed, no prop drilling.

React Problem: Manual error handling everywhere

function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchProducts = async () => {
setLoading(true);
setError(null);
try {
const data = await api.getProducts();
setProducts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchProducts();
}, []);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ProductGrid products={products} />;
}

TDI2 Solution: Service-level error management

@Service()
export class ProductService implements ProductServiceInterface {
state = {
products: [] as Product[],
loading: false,
error: null as string | null
};
async fetchProducts(): Promise<void> {
this.state.loading = true;
this.state.error = null;
try {
this.state.products = await this.productRepository.getAll();
} catch (error) {
this.state.error = error.message;
} finally {
this.state.loading = false;
}
}
}
function ProductList({ productService }: ServicesProps) {
const { products, loading, error } = productService.state;
useEffect(() => { productService.fetchProducts(); }, []);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <ProductGrid products={products} />;
}

Impact: Centralized error handling, reactive state updates, cleaner components.

React Problem: Manual form state management

function CheckoutForm() {
const [formData, setFormData] = useState({
email: '',
address: '',
creditCard: '',
errors: {}
});
const [submitting, setSubmitting] = useState(false);
const validate = (data) => {
const errors = {};
if (!data.email) errors.email = 'Email required';
if (!data.address) errors.address = 'Address required';
// ... more validation
return errors;
};
const handleSubmit = async (e) => {
e.preventDefault();
const errors = validate(formData);
if (Object.keys(errors).length) {
setFormData(prev => ({ ...prev, errors }));
return;
}
setSubmitting(true);
try {
await api.submitOrder(formData);
// Handle success
} catch (error) {
// Handle error
} finally {
setSubmitting(false);
}
};
// JSX with manual onChange handlers...
}

TDI2 Solution: Injected form service

@Service()
export class CheckoutFormService implements CheckoutFormServiceInterface {
state = {
formData: { email: '', address: '', creditCard: '' },
errors: {} as Record<string, string>,
submitting: false,
submitted: false
};
updateField(field: string, value: string): void {
this.state.formData[field] = value;
if (this.state.errors[field]) {
delete this.state.errors[field];
}
}
async submit(): Promise<void> {
const errors = this.validate();
if (Object.keys(errors).length) {
this.state.errors = errors;
return;
}
this.state.submitting = true;
try {
await this.orderService.create(this.state.formData);
this.state.submitted = true;
} catch (error) {
this.state.errors = { submit: error.message };
} finally {
this.state.submitting = false;
}
}
}
function CheckoutForm({ checkoutFormService }: ServicesProps) {
const { formData, errors, submitting } = checkoutFormService.state;
return (
<form onSubmit={() => checkoutFormService.submit()}>
<input
value={formData.email}
onChange={(e) => checkoutFormService.updateField('email', e.target.value)}
/>
{errors.email && <span className="error">{errors.email}</span>}
{/* Simplified form fields */}
</form>
);
}

Impact: Centralized form logic, automatic validation, reactive updates.

React Problem: Manual optimization with useMemo/useCallback

function ProductCatalog({ filters, sortOrder }) {
const [products, setProducts] = useState([]);
const filteredProducts = useMemo(() => {
return products.filter(product => {
return filters.category === 'all' || product.category === filters.category;
}).filter(product => {
return product.price >= filters.minPrice && product.price <= filters.maxPrice;
});
}, [products, filters]);
const sortedProducts = useMemo(() => {
return [...filteredProducts].sort((a, b) => {
switch (sortOrder) {
case 'price-asc': return a.price - b.price;
case 'price-desc': return b.price - a.price;
case 'name': return a.name.localeCompare(b.name);
default: return 0;
}
});
}, [filteredProducts, sortOrder]);
const handleCategoryChange = useCallback((category) => {
setFilters(prev => ({ ...prev, category }));
}, [setFilters]);
return <ProductGrid products={sortedProducts} onCategoryChange={handleCategoryChange} />;
}

TDI2 Solution: Automatic reactive optimization

@Service()
export class ProductCatalogService implements ProductCatalogServiceInterface {
state = {
allProducts: [] as Product[],
filters: { category: 'all', minPrice: 0, maxPrice: 1000 },
sortOrder: 'name' as SortOrder
};
get filteredAndSortedProducts(): Product[] {
const filtered = this.state.allProducts
.filter(p => this.state.filters.category === 'all' || p.category === this.state.filters.category)
.filter(p => p.price >= this.state.filters.minPrice && p.price <= this.state.filters.maxPrice);
return filtered.sort((a, b) => {
switch (this.state.sortOrder) {
case 'price-asc': return a.price - b.price;
case 'price-desc': return b.price - a.price;
case 'name': return a.name.localeCompare(b.name);
default: return 0;
}
});
}
updateFilters(updates: Partial<FilterOptions>): void {
Object.assign(this.state.filters, updates);
}
}
function ProductCatalog({ productCatalogService }: ServicesProps) {
const { filteredAndSortedProducts } = productCatalogService;
return (
<ProductGrid
products={filteredAndSortedProducts}
onCategoryChange={(category) => productCatalogService.updateFilters({ category })}
/>
);
}

Impact: Automatic memoization through Valtio, computed properties, no manual optimization.

Problem Areas:

  • Complex dependency arrays in useEffect
  • Stale closures and callback dependencies
  • Rules of Hooks constraints
  • Effect cleanup management

TDI2 Approach:

  • Services handle lifecycle separately from render cycle
  • Clear dependency injection eliminates closure issues
  • No hook rules - services are regular classes
  • Automatic cleanup through container lifecycle

Problem Areas:

  • Forgotten useEffect cleanup functions
  • Event listener management
  • Subscription handling
  • Component unmount races

TDI2 Solution:

  • Services implement lifecycle interfaces (OnDestroy)
  • Container manages cleanup automatically
  • Valtio subscriptions auto-managed
  • No manual cleanup in components

Error Boundaries Limitations (65% Reduction)

Section titled “Error Boundaries Limitations (65% Reduction)”

Problem Areas:

  • Cannot catch async errors
  • Limited to render-time errors
  • Poor error propagation
  • No global error handling

TDI2 Solution:

  • Services handle async errors directly
  • Error states are reactive and propagate automatically
  • Global error service can coordinate error handling
  • Better error boundaries with service integration
Pain PointReact ComplexityTDI2 ImpactCode Reduction
State Management🔴 9/10🟢 9/10-90%
Testing Challenges🔴 8/10🟢 9/10-80%
Component Coupling🔴 8/10🟢 9/10-75%
Form Handling🟡 7/10🟢 8/10-65%
Error Boundaries🟡 7/10🟢 8/10-65%
Async Error Handling🔴 8/10🟢 8/10-60%
Performance Issues🔴 8/10🟢 8/10-60%
Memory Leaks🟡 6/10🟢 7/10-50%
Hooks Complexity🔴 7/10🟢 7/10-45%

For teams experiencing React pain points, TDI2 provides:

  1. Immediate Relief - Reduced development time and debugging
  2. Long-term Sustainability - Better architecture scales with team growth
  3. Developer Experience - Familiar patterns for backend developers
  4. Performance Gains - Automatic optimization through reactive architecture
  5. Testing Efficiency - Isolated, mockable services

Teams can adopt TDI2 incrementally:

  1. Start Small - Convert one complex component/feature area
  2. Measure Impact - Track time savings and bug reduction
  3. Team Training - Educate on dependency injection patterns
  4. Gradual Expansion - Migrate additional areas as team gains confidence
  5. Full Adoption - Enterprise-wide implementation with proven ROI

The pain point solutions aren’t theoretical - they represent measurable improvements in development velocity, code quality, and team productivity.