# Reactive Dataset Queries

Reactive dataset queries are are key component of Flo.w RDF reactivity. They allow a developer to use the declarative style of programming to bind UI actions to data queries, and bind the results of those queries to UI updates. This frees the developer from having to explicitly implement UI event handlers and control logic.

Flo.w RDF will ensure that expensive dataset queries are only run when their inputs change. Query results are cached and re-used until an input change triggers a re-evaluation of the query.

See the FlowQuery class in the Flo.w RDF JavaScript reference.

The diagram below shows an example of the sequence of events that allows a map to be reactively updated following an action by the user in a Flo.w web application.

  1. The user selects an option in the UI.
  2. Application state is updated
  3. The reactive query is triggered .
  4. The dataset query is issued to Flo.w Engine and results are returned.
  5. The map is updated.

Reactive Updates

# Registering A Reactive Query

To register a reactive query, use the registerQuery method of the StateStore instance available from global context.

An example query registration:

// Register a reactive query to return air quality data where no2 > state.filterValue
context.state.registerQuery('queries.filteredAirQuality', state => ({
  datasetId: 'airquality',
  where: {no2: {$gt: state.filterValue}}, // Accessing reactive `filterValue` property
  limit: 10,
  format: 'geojson'
}));

Query Names

By convention, reactive queries are grouped under the queries top-level application state property.

# The Query Definition

A query definition is supplied when a query is registered. This specifies which dataset to query and what query operations to perform. The definition can reference reactive application state. When any referenced state property changes, the reactive query will be re-evaluated.

See Querying Datasets for a complete description of Flo.w query definitions.

Computed Properties

When accessing computed properties use state.propName.get() to access the computed value.

# Transforming Query Results

An optional transformation function can be specified when registering a reactive query. Use this to transform query results before they are returned to Flo.w RDF.

An example query registration with a transformation function:

// Register a reactive query to return air quality data point with max NO2 value.
context.state.registerQuery('queries.maxNO2',
state => ({
  datasetId: 'airquality',
  where: {no2: {$not: null}},
  order: [['no2', 'desc']]
  limit: 1
}),
results => {
  // Transform results to extract single data object from result array
  if (results.length) {
    return results[0];
  } else {
    return null;
  }
});

# Using Query Results

Query results are available as the results property of the reactive query. This property is reactive and can be bound to UI components or used by computed application state properties. When the query is re-evaluated, bound UI components or dependent computed properties will be automatically updated with the new results.

Query results can be bound to:

# Busy State

The busy property of a reactive query is set to true when the query is running. You can bind this reactive property to UI elements to indicate that the query is active:

  • Busy Overlay - inactive portions of the UI when a query is running.
  • Progress Bar - show an indeterminate progress bar when a query is running.
  • Progress Ring - show a spinner when a query is running.

To combine the busy state of multiple reactive queries to update a global busy UI component, use a computed property. For example:

context.state.compute('globalBusy', state =>
  state.queries.query1.busy || state.queries.query2.busy);

# Disabling a Query

Generally, a query should re-run reactively whenever any of its dependencies change. However, in certain circumstances you may wish to disable a query. For example, you may want an expensive query to only run in a particular application mode or when other conditions are met.

To prevent a query from running return null from the query definition function supplied when you register the query. The results property of the query will be immediately set to null without issuing a query to Flo.w Engine. Note that if you have specified a result transformation function then the function will be run as usual giving you the option to transform the null result to meet the needs of your application (for example, an empty GeoJSON FeatureCollection).

// Register a reactive query to that is bypassed if mode is not 'modeA'.
context.state.registerQuery('queries.query1',
state => {
  if (state.mode !== 'modeA') {
    return null;
  }

  return {
    datasetId: 'myDataset',
    where: {timestamp: state.time},
    format: 'geojson'
  }
},
results => {
  // Transform null result to an empty feature collection
  return results || {type: 'FeatureCollection', features: []};
});

# Cancelling a Query

In certain circumstances you may wish to allow a user to cancel a long-running query. Call the FlowQuery.cancel() method as follows:

// Cancel a long-running query in response to user request
cancelQueryHandler() {
  const query = this.flowContext.state.get(['queries', 'myQuery']);
  query.cancel();
}

Cancelling a query immediately sets the busy flag to false. The query results property will remain unchanged.

# Handling Errors

The error property of a reactive query can be used to respond to query errors. The property is set to null when a query is initiated and will be set to a JavaScript Error instance if an error occurs. You can respond to changes in the error property using the StateStore.toStream() method and display a snack-bar, dialog or other error notification.

To combine the error state of multiple reactive queries, use a computed property. For example:

context.state.compute('globalError', state =>
  state.queries.query1.error || state.queries.query2.error);

# Setting the Flo.w API Key

By default, reactive queries will be executed using the application-wide API key specified when initializing the Flo.w RDF context.

To override the API key for a reactive query (for example, to query data from another Flo.w application), specify the API key as an additional parameter as shown below:

context.state.registerQuery('queries.airquality', state => ({
  datasetId: 'airquality',
  limit: 10,
  params: { apiKey: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'}
}));