Consuming Variables

In Flowgram, managing and consuming variables is at the core of building dynamic, interactive applications. Understanding how to effectively consume variables is crucial for developers. This document will guide you through the various ways to consume variables in different scenarios.

Getting the Variable Tree within a Node

In the nodes on the canvas, we often need to get the variables available in the current scope and display them in a tree structure for users to select and operate. The useAvailableVariables React Hook is designed for this purpose.

useAvailableVariables

useAvailableVariables is a lightweight Hook that directly returns an array of available variables (VariableDeclaration[]) in the current scope. If you only need a simple list of variables and don't need to perform more complex operations, useAvailableVariables is a more convenient choice.

use-variable-tree.tsx
import {
  type BaseVariableField,
  useAvailableVariables,
} from '@flowgram.ai/fixed-layout-editor';

// .... In a React component or Hook

const availableVariables = useAvailableVariables();

const renderVariable = (variable: BaseVariableField) => {
  // You can render each variable according to your needs here
  // ....
}

return availableVariables.map(renderVariable);

// ....

Drilling Down into Object Type Variables

When a variable's type is Object, we often need to "drill down" into it to get its properties. The ASTMatch.isObject method can help us determine if a variable's type is an object. If it is, we can recursively render its properties.

use-variable-tree.tsx
import {
  type BaseVariableField,
  ASTMatch,
} from '@flowgram.ai/fixed-layout-editor';

// ....

const renderVariable = (variable: BaseVariableField) => ({
  title: variable.meta?.title,
  key: variable.key,
  // Only Object type variables can be drilled down
  children: ASTMatch.isObject(variable.type) ? variable.type.properties.map(renderVariable) : [],
});

// ....

Drilling Down into Array Type Variables

Similar to the Object type, when we encounter an Array type variable, we also want to display its internal structure. For arrays, we are usually concerned with the type of its elements. ASTMatch.isArray can determine if a variable's type is an array. It's worth noting that the element type of an array can be anything, even another array. Therefore, we need a recursive helper function getTypeChildren to handle this situation.

use-variable-tree.tsx
import {
  type BaseVariableField,
  type BaseType,
  ASTMatch,
} from '@flowgram.ai/fixed-layout-editor';

// ....

const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
  if (!type) return [];

  // Get the properties of an Object
  if (ASTMatch.isObject(type)) return type.properties;

  // Recursively get the element type of an Array
  if (ASTMatch.isArray(type)) return getTypeChildren(type.items);

  return [];
};

const renderVariable = (variable: BaseVariableField) => ({
  title: variable.meta?.title,
  key: variable.key,
  children: getTypeChildren(variable.type).map(renderVariable),
});

// ....

Official Material: VariableSelector

To make it easier for you to integrate variable selection functionality into your application, we have prepared an official material for you - the VariableSelector component. It encapsulates all the logic mentioned earlier, allowing you to have a powerful and beautiful variable selector without starting from scratch.

VariableSelector not only supports displaying a variable tree but also has built-in advanced features such as search and filtering, which can greatly enhance the user experience. For a more detailed introduction, please refer to the Official Form Materials documentation.

You can use it in the following two ways:

1. By Referencing the NPM Package

This is the simplest and most direct way. With just one line of code, you can introduce VariableSelector into your project.

import { VariableSelector } from '@flowgram.ai/form-materials';

2. By Copying the Source Code via CLI

If you want to customize VariableSelector more deeply, we also provide a way to copy the component's source code directly into your project via the CLI. This way, you can modify it as you wish to meet your unique business needs.

npx @flowgram.ai/materials components/variable-selector

ScopeAvailableData: Your Variable Toolbox

The ScopeAvailableData object is one of the core components of the variable system. It is returned by the useScopeAvailable Hook and is your main bridge for interacting with the available variables in the scope. You can think of it as a powerful "variable toolbox."

useScopeAvailable

useScopeAvailable is a more powerful Hook that returns a ScopeAvailableData object, which not only contains all the available variable information in the current scope but also provides some advanced APIs, such as trackByKeyPath.

Its main differences from useAvailableVariables are:

  • Return Value: useAvailableVariables directly returns a variable array, while useScopeAvailable returns a ScopeAvailableData object that contains the variables property and other methods.
  • Applicable Scenarios: When you need to perform more complex operations on variables, such as tracking changes to a single variable, useScopeAvailable is your best choice.
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';

const available = useScopeAvailable();

// The available object contains the variable list and other APIs
console.log(available.variables);

Getting the Variable List

The most basic usage is to get all the available variables in the current scope.

import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';

function MyComponent() {
  const available = useScopeAvailable();

  // available.variables is an array containing all available variables
  console.log(available.variables);

  return (
    <ul>
      {available.variables.map(variable => (
        <li key={variable.key}>{variable.name}</li>
      ))}
    </ul>
  );
}

Tracking Changes to a Single Variable: trackByKeyPath

When you are only concerned with the changes of a specific variable (especially one nested in an Object or Array), trackByKeyPath comes in handy. It allows you to accurately "subscribe" to the updates of this variable without causing the component to re-render due to changes in other unrelated variables, thus achieving finer performance optimization.

Suppose we have an Object type variable named user with a name property. We want to update the component when user.name changes.

import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
import { useEffect, useState } from 'react';

function UserNameDisplay() {
  const available = useScopeAvailable();
  const [userName, setUserName] = useState('');

  useEffect(() => {
    // Define the variable path we want to track
    const keyPath = ['user', 'name'];

    // Start tracking!
    const disposable = available.trackByKeyPath(keyPath, (nameField) => {
      // This callback function will be triggered when user.name changes
      // nameField is the changed variable field, from which we can get the latest default value
      setUserName(nameField?.meta.default || '');
    });

    // When the component unmounts, don't forget to cancel the tracking to avoid memory leaks
    return () => disposable.dispose();
  }, [available]); // The dependency is the available object

  return <div>User Name: {userName}</div>;
}

Advanced Event Listening API

In addition to trackByKeyPath, ScopeAvailableData also provides a set of lower-level event listening APIs that allow you to control the response logic to variable changes more finely. This is very useful when dealing with complex scenarios that require manual subscription management.

Let's use a table to compare these three core listening APIs in detail:

APITriggerCallback ParametersCore Differences and Applicable Scenarios
onVariableListChangeWhen the list structure of available variables changes.(variables: VariableDeclaration[]) => voidOnly cares about the list itself. For example, an upstream node adds/deletes an output variable, causing the total number or members of available variables to change. It does not care about internal or drilled-down changes to variables. Suitable for scenarios where the UI needs to be updated based on the presence or number of variables in the list.
onAnyVariableChangeWhen the content (meta, type, drilldown fields) of any variable in the list changes.(changedVariable: VariableDeclaration) => voidOnly cares about content (type, meta, drilldown) updates to variables. For example, a user modifies the type of an output variable. It does not care about changes to the list structure. Suitable for scenarios where you need to react to changes in the content of any variable.
onListOrAnyVarChangeWhen either of the above two situations occurs.(variables: VariableDeclaration[]) => voidThe most comprehensive listener, a combination of the previous two. It is triggered by either a change in the list structure or a change in the value of any variable. Suitable for "catch-all" scenarios where you need to respond to any possible changes.

Code Example

Let's look at a specific example of how to use these APIs in a component.

import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
import { useEffect } from 'react';

function AdvancedListenerComponent() {
  const available = useScopeAvailable();

  useEffect(() => {
    // 1. Listen for changes in the list structure
    const listChangeDisposable = available.onVariableListChange((variables) => {
      console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
    });

    // 2. Listen for changes in the value of any variable
    const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
      console.log(`The content of variable '${changedVariable.keyPath.join('.')}' has changed!`);
    });

    // 3. Listen for all changes (structure or content)
    const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
      console.log('The variable list or the content of one of its variables has changed!');
      // Note: The callback parameter here is the complete variable list, not the single changed variable
    });

    // When the component unmounts, be sure to clean up all listeners to prevent memory leaks
    return () => {
      listChangeDisposable.dispose();
      valueChangeDisposable.dispose();
      allChangesDisposable.dispose();
    };
  }, [available]);

  return <div>Please check the console for logs of variable changes...</div>;
}

Key Points:

  • These APIs all return a Disposable object.
  • To avoid memory leaks and unnecessary calculations, you must call its dispose() method in the cleanup function of useEffect to cancel the listener.