Migrate React components from Snowflake SQL queries to NQL using the useNqlQuery hook. Handles query design, case sensitivity, and React dependency optimization.
Migrate the queries used in: $ARGUMENTS from using Snowflake SQL to NQL.
This guide provides instructions for migrating React components from SQL-based data fetching to NQL-based queries using the useNqlQuery hook.
NQL (Narrative Query Language) queries create temporary materialized views and then sample the data, requiring a different approach than direct SQL execution. The process involves:
Look for:
useDatasetRows, useSqlQuery)Example Pattern:
// OLD: Direct data fetching + client-side processing
const { data: rawData } = useDatasetRows(datasetName);
const processedData = rawData?.map(row => /* complex processing */) || [];
Do:
as label_value)Don't:
useNqlQuery)company_dataExample:
SELECT DISTINCT
CONCAT_WS(' > ', CATEGORY, SUB_CATEGORY) as label_value
FROM company_data.{datasetName}
WHERE CONCAT_WS(' > ', CATEGORY, SUB_CATEGORY) IS NOT NULL
AND CONCAT_WS(' > ', CATEGORY, SUB_CATEGORY) <> ''
LIMIT 1000
Structure:
export function useCustomNqlQuery(
// Stable primitive parameters
param1: string | undefined,
param2: number | null,
// Complex objects (handle carefully)
complexData: ComplexType[],
): QueryResult {
// Extract stable dependencies first
const stableDependency = useMemo(() => {
return complexData.find(item => item.id === param1)?.relevantField;
}, [param1, complexData]);
// Build query with stable dependencies
const nqlQuery = useMemo(() => {
if (!param1 || !stableDependency) return "";
return `
SELECT column1, column2
FROM dataset.${param1}
WHERE condition = '${stableDependency}'
LIMIT 1000
`.trim();
}, [param1, stableDependency]);
const { data: rawData, error, isLoading } = useNqlQuery(nqlQuery, {
enabled: !!nqlQuery,
});
// Memoize data transformation
const transformedData = useMemo(() => {
if (!rawData) return null;
return rawData.map((row, index) => ({
id: `${row.COLUMN1}_${index}`, // Stable, unique IDs
value: String(row.COLUMN1 || row.column1 || ""),
// ... other transformations
}));
}, [rawData]);
return { data: transformedData, error, isLoading };
}
Issue: NQL results may return uppercase column names even if your query uses lowercase aliases.
Solution:
// Handle both cases in data access
const value = String(row.LABEL_VALUE || row.label_value || "");
Type Definition:
type NqlResult = {
LABEL_VALUE?: string; // Uppercase (what may be returned)
label_value?: string; // Lowercase (what you expect)
};
Critical Issues to Avoid:
Problem: Large objects in dependencies cause constant re-computation
// BAD: Entire array causes re-renders
const query = useMemo(() => buildQuery(), [largeArray]);
Solution: Extract only what you need
// GOOD: Stable dependencies
const specificItem = useMemo(() =>
largeArray.find(item => item.id === targetId),
[targetId, largeArray]
);
const query = useMemo(() => buildQuery(specificItem), [specificItem]);
Problem: Data transformation creates new objects on every render
// BAD: Creates new objects every render
const data = rawData?.map(row => ({ ...row, processed: true }));
Solution: Memoize transformations
// GOOD: Stable object references
const data = useMemo(() =>
rawData?.map(row => ({ ...row, processed: true })),
[rawData]
);
Replace old hook calls:
// OLD
const { data, isLoading, error } = useDatasetRows(datasetId);
// NEW
const { data, isLoading, error } = useCustomNqlQuery(
selectedAttribute,
datasetName,
referenceDatasets
);
Update error handling:
// Handle NQL-specific errors
useEffect(() => {
if (error) {
dispatch({
type: "ui/setError",
payload: `Failed to load data: ${error.message}`,
});
}
}, [error, dispatch]);
Add Temporary Debugging:
console.log('[CustomNqlQuery] Query:', nqlQuery);
console.log('[CustomNqlQuery] Raw data:', rawData);
console.log('[CustomNqlQuery] Transformed:', transformedData);
Monitor for:
Remove debugging once stable
Cause: Unstable dependencies in useMemo/useCallback Solution: Use primitive values or properly memoized objects as dependencies
Cause: New datasets not appearing in dropdowns
Solution: Cache invalidation Usually resolves automatically; avoid manual cache invalidation
Cause: Expecting lowercase but getting uppercase column names Solution: Handle both cases in data access and type definitions
Cause: Parent components passing unstable props Solution: Memoize expensive computations and use stable object references
const { data: datasetRows } = useDatasetRows(datasetName);
const uniqueLabels = useMemo(() => {
// Complex client-side processing
const values = new Map();
datasetRows?.forEach(row => {
const value = row[columnName];
if (value) values.set(value, (values.get(value) || 0) + 1);
});
return Array.from(values.entries()).map(([value, count], index) => ({
id: `label_${index}`,
value,
count
}));
}, [datasetRows, columnName]);
const { data: uniqueLabels } = useNqlLabelsQuery(
selectedAttribute,
datasetName,
referenceDatasets
);
// Much simpler - processing moved to database level
// Returns stable, memoized results