React Integration

Learn how to integrate the Qlik TypeScript SDK with React applications using modern patterns, hooks, and components.

Getting Started

Installation

bash
# Create React app with TypeScript
npx create-react-app my-qlik-app --template typescript
cd my-qlik-app

# Install Qlik SDK
npm install qlik

# Install additional dependencies for advanced features
npm install @types/react @types/react-dom

Custom React Hooks

useQlik Hook

Main hook for managing Qlik SDK instance and authentication

typescript
// hooks/useQlik.ts
import { useState, useEffect, useCallback } from 'react';
import Qlik from 'qlik';

interface UseQlikOptions {
  host: string;
  webIntegrationId: string;
  autoAuthenticate?: boolean;
}

interface UseQlikReturn {
  qlik: Qlik | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  error: string | null;
  authenticate: () => Promise<void>;
  logout: () => Promise<void>;
}

export function useQlik({
  host,
  webIntegrationId,
  autoAuthenticate = true
}: UseQlikOptions): UseQlikReturn {
  const [qlik, setQlik] = useState<Qlik | null>(null);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const authenticate = useCallback(async () => {
    if (!qlik) return;

    try {
      setIsLoading(true);
      setError(null);
      await qlik.authenticateToQlik();
      setIsAuthenticated(true);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Authentication failed');
      setIsAuthenticated(false);
    } finally {
      setIsLoading(false);
    }
  }, [qlik]);

  const logout = useCallback(async () => {
    if (!qlik) return;

    try {
      await qlik.logout();
      setIsAuthenticated(false);
    } catch (err) {
      console.error('Logout failed:', err);
    }
  }, [qlik]);

  useEffect(() => {
    const initializeQlik = async () => {
      try {
        const qlikInstance = new Qlik({ host, webIntegrationId });
        setQlik(qlikInstance);

        if (autoAuthenticate) {
          const authStatus = await qlikInstance.isAuthenticated();
          if (authStatus) {
            setIsAuthenticated(true);
          } else {
            await qlikInstance.authenticateToQlik();
            setIsAuthenticated(true);
          }
        }
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Failed to initialize Qlik');
      } finally {
        setIsLoading(false);
      }
    };

    initializeQlik();
  }, [host, webIntegrationId, autoAuthenticate]);

  return {
    qlik,
    isAuthenticated,
    isLoading,
    error,
    authenticate,
    logout
  };
}

React Components

QlikProvider Component

Context provider for sharing Qlik instance across components

typescript
// components/QlikProvider.tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { useQlik } from '../hooks/useQlik';

interface QlikContextValue {
  qlik: any | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  error: string | null;
  authenticate: () => Promise<void>;
  logout: () => Promise<void>;
}

const QlikContext = createContext<QlikContextValue | undefined>(undefined);

interface QlikProviderProps {
  children: ReactNode;
  config: {
    host: string;
    webIntegrationId: string;
  };
}

export function QlikProvider({ children, config }: QlikProviderProps) {
  const qlikState = useQlik(config);

  return (
    <QlikContext.Provider value={qlikState}>
      {children}
    </QlikContext.Provider>
  );
}

export function useQlikContext() {
  const context = useContext(QlikContext);
  if (context === undefined) {
    throw new Error('useQlikContext must be used within a QlikProvider');
  }
  return context;
}

// Usage in App.tsx
export default function App() {
  return (
    <QlikProvider
      config={{
        host: process.env.REACT_APP_QLIK_HOST!,
        webIntegrationId: process.env.REACT_APP_QLIK_WEB_INTEGRATION_ID!
      }}
    >
      <Dashboard />
    </QlikProvider>
  );
}

QlikChart Component

Reusable component for displaying Qlik visualizations

typescript
// components/QlikChart.tsx
import React, { useRef, useEffect, useState } from 'react';
import { useQlikContext } from './QlikProvider';
import { useQlikApp } from '../hooks/useQlikApp';
import { useQlikObject } from '../hooks/useQlikObject';

interface QlikChartProps {
  appId: string;
  objectId: string;
  height?: number;
  className?: string;
  onError?: (error: string) => void;
}

export function QlikChart({ 
  appId, 
  objectId, 
  height = 400, 
  className = '',
  onError 
}: QlikChartProps) {
  const chartRef = useRef<HTMLDivElement>(null);
  const { qlik, isAuthenticated } = useQlikContext();
  const { app, isLoading: appLoading, error: appError } = useQlikApp({ qlik, appId });
  const { object, layout, isLoading: objectLoading, error: objectError } = useQlikObject({ 
    app, 
    objectId 
  });

  const [isRendered, setIsRendered] = useState(false);

  useEffect(() => {
    if (appError || objectError) {
      onError?.(appError || objectError || 'Unknown error');
    }
  }, [appError, objectError, onError]);

  useEffect(() => {
    if (!object || !chartRef.current || !isAuthenticated || isRendered) return;

    const renderChart = async () => {
      try {
        await object.show(chartRef.current);
        setIsRendered(true);
      } catch (error) {
        console.error('Failed to render chart:', error);
        onError?.(`Failed to render chart: ${error.message}`);
      }
    };

    renderChart();
  }, [object, isAuthenticated, isRendered, onError]);

  if (!isAuthenticated) {
    return (
      <div className="flex items-center justify-center h-64 bg-gray-100 rounded">
        <p>Authentication required</p>
      </div>
    );
  }

  if (appLoading || objectLoading) {
    return (
      <div className="flex items-center justify-center bg-gray-100 rounded" style={{ height }}>
        <div className="flex flex-col items-center space-y-2">
          <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
          <p className="text-sm text-gray-600">Loading chart...</p>
        </div>
      </div>
    );
  }

  if (appError || objectError) {
    return (
      <div className="flex items-center justify-center bg-red-50 border border-red-200 rounded" style={{ height }}>
        <p className="text-red-600">Error loading chart: {appError || objectError}</p>
      </div>
    );
  }

  return (
    <div className={className}>
      {layout && (
        <div className="mb-2">
          <h3 className="text-lg font-semibold">{layout.title}</h3>
          {layout.subtitle && (
            <p className="text-sm text-gray-600">{layout.subtitle}</p>
          )}
        </div>
      )}
      <div 
        ref={chartRef} 
        style={{ height }} 
        className="qlik-chart-container border rounded"
      />
    </div>
  );
}

QlikTable Component

Component for displaying Qlik data in table format

typescript
// components/QlikTable.tsx
import React from 'react';
import { useQlikContext } from './QlikProvider';
import { useQlikApp } from '../hooks/useQlikApp';
import { useQlikObject } from '../hooks/useQlikObject';

interface QlikTableProps {
  appId: string;
  objectId: string;
  className?: string;
  maxRows?: number;
}

export function QlikTable({ 
  appId, 
  objectId, 
  className = '', 
  maxRows = 100 
}: QlikTableProps) {
  const { qlik } = useQlikContext();
  const { app } = useQlikApp({ qlik, appId });
  const { layout, data, isLoading, error } = useQlikObject({ app, objectId });

  if (isLoading) {
    return (
      <div className="animate-pulse">
        <div className="h-4 bg-gray-200 rounded mb-2"></div>
        <div className="h-4 bg-gray-200 rounded mb-2"></div>
        <div className="h-4 bg-gray-200 rounded mb-2"></div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="p-4 bg-red-50 border border-red-200 rounded">
        <p className="text-red-600">Error: {error}</p>
      </div>
    );
  }

  if (!layout || !data) {
    return (
      <div className="p-4 bg-gray-50 border border-gray-200 rounded">
        <p className="text-gray-600">No data available</p>
      </div>
    );
  }

  const headers = layout.qHyperCube?.qDimensionInfo
    ?.map((dim: any) => dim.qFallbackTitle)
    .concat(layout.qHyperCube?.qMeasureInfo?.map((measure: any) => measure.qFallbackTitle)) || [];

  const displayData = data.slice(0, maxRows);

  return (
    <div className={className}>
      {layout.title && (
        <h3 className="text-lg font-semibold mb-4">{layout.title}</h3>
      )}
      
      <div className="overflow-x-auto">
        <table className="min-w-full border-collapse border border-gray-200">
          <thead>
            <tr className="bg-gray-50">
              {headers.map((header, index) => (
                <th key={index} className="border border-gray-200 px-4 py-2 text-left">
                  {header}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {displayData.map((row, rowIndex) => (
              <tr key={rowIndex} className="hover:bg-gray-50">
                {row.map((cell: any, cellIndex: number) => (
                  <td key={cellIndex} className="border border-gray-200 px-4 py-2">
                    {cell.qText || cell.qNum?.toLocaleString() || '-'}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {data.length > maxRows && (
        <p className="text-sm text-gray-600 mt-2">
          Showing {maxRows} of {data.length} rows
        </p>
      )}
    </div>
  );
}

Complete Dashboard Example

Full React Dashboard

Complete example showing all components working together

typescript
// Dashboard.tsx
import React, { useState } from 'react';
import { useQlikContext } from './components/QlikProvider';
import { QlikChart } from './components/QlikChart';
import { QlikTable } from './components/QlikTable';

export function Dashboard() {
  const { qlik, isAuthenticated, isLoading, error, authenticate, logout } = useQlikContext();
  const [selectedAppId, setSelectedAppId] = useState('your-app-id');
  const [chartError, setChartError] = useState<string | null>(null);

  if (isLoading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="text-center">
          <h2 className="text-2xl font-bold text-red-600 mb-4">Connection Error</h2>
          <p className="text-gray-600 mb-4">{error}</p>
          <button
            onClick={authenticate}
            className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
          >
            Retry Authentication
          </button>
        </div>
      </div>
    );
  }

  if (!isAuthenticated) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="text-center">
          <h2 className="text-2xl font-bold mb-4">Authentication Required</h2>
          <button
            onClick={authenticate}
            className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
          >
            Sign In to Qlik
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white shadow">
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
          <div className="flex justify-between items-center py-6">
            <h1 className="text-3xl font-bold text-gray-900">Sales Dashboard</h1>
            <button
              onClick={logout}
              className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
            >
              Logout
            </button>
          </div>
        </div>
      </header>

      <main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
        {chartError && (
          <div className="mb-4 p-4 bg-red-50 border border-red-200 rounded">
            <p className="text-red-600">Chart Error: {chartError}</p>
            <button 
              onClick={() => setChartError(null)}
              className="text-sm underline"
            >
              Dismiss
            </button>
          </div>
        )}

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
          <div className="bg-white p-6 rounded-lg shadow">
            <QlikChart
              appId={selectedAppId}
              objectId="sales-kpi"
              height={300}
              onError={setChartError}
            />
          </div>
          
          <div className="bg-white p-6 rounded-lg shadow">
            <QlikChart
              appId={selectedAppId}
              objectId="revenue-trend"
              height={300}
              onError={setChartError}
            />
          </div>
        </div>

        <div className="bg-white rounded-lg shadow">
          <div className="p-6">
            <QlikTable
              appId={selectedAppId}
              objectId="sales-table"
              maxRows={50}
            />
          </div>
        </div>
      </main>
    </div>
  );
}

🚀 React Integration Best Practices

Context Usage: Use React Context for sharing Qlik instances across components
Error Boundaries: Implement error boundaries to catch and handle Qlik-related errors
Loading States: Always provide loading indicators for better UX
Memory Management: Clean up subscriptions and objects in useEffect cleanup
Performance: Memoize expensive calculations and use React.memo for pure components
Type Safety: Use TypeScript for better development experience and error catching