# Best Practices

Recommended patterns and practices for working with the Elements SDK.

## Initialization

### Initialize Once

Create a single client instance and reuse it:

```javascript
// Good
const client = await Elements('KEY', { env: 'production' });
await client.injectProductElement([...]);
await client.injectCartElement('cart');

// Bad - creates multiple instances
await Elements('KEY', { env: 'production' });
await Elements('KEY', { env: 'production' });
```

### Use Global Access

After initialization, use `window.LiquidCommerce.elements`:

```javascript
// Initialize once
await Elements('KEY', { env: 'production' });

// Use globally throughout your app
window.LiquidCommerce.elements.actions.cart.openCart();
```

### Wait for Client Ready

```javascript
if (window.LiquidCommerce.elements) {
  // Client already ready
  window.LiquidCommerce.elements.actions.cart.openCart();
} else {
  // Wait for initialization
  window.addEventListener('lce:actions.client_ready', () => {
    window.LiquidCommerce.elements.actions.cart.openCart();
  });
}
```

## Component Injection

### Verify Container Exists

```javascript
// Check container before injection
if (document.getElementById('product')) {
  await client.injectProductElement([
    { containerId: 'product', identifier: '123' }
  ]);
}
```

### Provide Adequate Space

```css
#product-container {
  min-height: 600px;  /* Prevent layout shift */
  max-width: 1200px;
}
```

### Handle Loading States

```javascript
showLoader('product-container');

await client.injectProductElement([...]);

// Loader automatically replaced when product loads
```

## Error Handling

### Catch Errors Appropriately

```javascript
try {
  await window.LiquidCommerce.elements.actions.cart.addProduct([...]);
} catch (error) {
  if (error.name === 'SDKError') {
    console.error('SDK Error:', error);
  }
}
```

### Provide Fallbacks

```javascript
const client = await Elements('KEY', { env: 'production' });

if (!client) {
  // Initialization failed
  showFallbackUI();
  return;
}
```

## Performance

### Lazy Load Products

```javascript
const observer = new IntersectionObserver(async (entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      const container = entry.target;
      await client.injectProductElement([
        { containerId: container.id, identifier: container.dataset.productId }
      ]);
      observer.unobserve(container);
    }
  }
});

document.querySelectorAll('.product-placeholder').forEach(el => {
  observer.observe(el);
});
```

### Use Checkout-Only Build

For checkout pages:

```javascript
import { ElementsCheckout } from '@liquidcommerce/elements-sdk/checkout';
```

Reduces bundle size by ~60%.

### Defer Non-Critical Operations

```javascript
// Load visible products first
await client.injectProductElement([
  { containerId: 'hero-product', identifier: '001' }
]);

// Load others after delay
setTimeout(async () => {
  await client.injectProductElement([
    { containerId: 'related-1', identifier: '002' }
  ]);
}, 1000);
```

## State Management

### Pre-fill Known Data

```javascript
// Set address if known
if (userSavedAddress) {
  await window.LiquidCommerce.elements.actions.address.setAddressManually(
    userSavedAddress,
    userCoordinates
  );
}

// Pre-fill checkout info
window.addEventListener('lce:actions.checkout_opened', () => {
  if (userIsLoggedIn) {
    window.LiquidCommerce.elements.actions.checkout.updateCustomerInfo({
      firstName: user.firstName,
      email: user.email
    });
  }
});
```

### Don't Clear Cart Unnecessarily

Cart persists across sessions - avoid clearing unless intentional:

```javascript
// Only clear when user explicitly requests
document.getElementById('clear-cart-btn').addEventListener('click', async () => {
  if (confirm('Clear cart?')) {
    await window.LiquidCommerce.elements.actions.cart.resetCart();
  }
});
```

## Cleanup

### Destroy Components When Done

In single-page applications, clean up when navigating away:

```javascript
// Destroy individual component
const component = client.getInjectedComponents().get('product-1');
component?.destroy();

// Destroy entire client (on route change)
client.destroy();
```

### Avoid Leaking References

```javascript
// Good - clean up on unmount
useEffect(() => {
  const init = async () => {
    const c = await Elements('KEY', { env: 'production' });
    clientRef.current = c;
  };
  init();
  return () => clientRef.current?.destroy();
}, []);
```

## Events

### Use Events for Reactive Updates

```javascript
// Update UI when cart changes
window.addEventListener('lce:actions.cart_updated', () => {
  const cart = window.LiquidCommerce.elements.actions.cart.getDetails();
  updateHeaderCartCount(cart.itemsCount);
});
```

### Track Important Events

```javascript
window.addEventListener('lce:actions.checkout_submit_completed', (event) => {
  const { orderId, total } = event.detail.data;
  
  // Analytics
  gtag('event', 'purchase', {
    transaction_id: orderId,
    value: total / 100
  });
  
  // Backend tracking
  fetch('/api/track-order', {
    method: 'POST',
    body: JSON.stringify({ orderId, total })
  });
});
```

## Security

### Use Proxy in Production

```javascript
const client = await Elements('YOUR_API_KEY', {
  env: 'production',
  proxy: {
    baseUrl: 'https://yoursite.com/api/elements-proxy'
  }
});
```

Prevents ad blockers and protects API keys.

### Separate API Keys by Environment

```javascript
const apiKey = process.env.NODE_ENV === 'production'
  ? 'prod_key_xyz'
  : 'dev_key_abc';

const client = await Elements(apiKey, {
  env: process.env.NODE_ENV === 'production' ? 'production' : 'development'
});
```

## Testing

### Use Development Mode

```javascript
const client = await Elements('YOUR_API_KEY', {
  env: 'development',
  debugMode: 'console',  // or 'panel'
  development: {
    openShadowDom: true  // Makes debugging easier
  }
});
```

### Skip card entry in checkout tests

Pre-select a saved payment method so the checkout payment step shows a read-only summary and never mounts the Stripe card form. Pass a payment method id the backend already has for the checkout's customer:

```javascript
window.LiquidCommerce.elements.actions.checkout.setSavedPaymentMethod({
  id: 'pm_123',
  card: { brand: 'Visa', last4: '4242', expMonth: '12', expYear: '30' }
});
```

`setSavedPaymentMethod` is a production action (also exposed on the checkout-only client), not test-only — beyond tests, use it to pre-fill a returning shopper's saved card. See [Checkout Actions](../api/actions/checkout-actions.md#actionscheckoutsetsavedpaymentmethod).

### Handle Test Scenarios

```javascript
// Test empty cart
if (isDevelopment) {
  await window.LiquidCommerce.elements.actions.cart.resetCart();
}

// Test with specific product
await window.LiquidCommerce.elements.actions.cart.addProduct([
  { identifier: testProductId, fulfillmentType: 'shipping', quantity: 1 }
]);
```

## Accessibility

### Provide Labels

```html
<label for="cart-toggle">Shopping Cart</label>
<button id="cart-toggle" data-lce-cart-toggle-button>
  Cart (<span data-lce-cart-items-count></span>)
</button>
```

### Use Semantic HTML

```html
<nav aria-label="Main navigation">
  <div id="cart-button"></div>
</nav>
```

## Mobile Optimization

### Responsive Containers

```css
@media (max-width: 768px) {
  #product-container {
    padding: 10px;
  }
}
```

### Mobile-Specific Cart Button

```javascript
if (window.innerWidth < 768) {
  window.LiquidCommerce.elements.ui.floatingCartButton(true);
} else {
  window.LiquidCommerce.elements.ui.cartButton('header-cart', true);
}
```

## See Also

- [Performance Guide](../reference/performance.md)
- [Troubleshooting](../reference/troubleshooting.md)
- [Component Guides](../guides/)
