Back to Lobehub

Data Store Selector

docs/development/state-management/state-management-selectors.mdx

2.2.105.4 KB
Original Source

Data Store Selector

Selectors are data retrieval modules under the LobeHub data flow development framework. Their role is to extract data from the store using specific business logic for consumption by components.

Taking src/store/tool/slices/plugin/selectors.ts as an example:

This TypeScript code snippet defines an object named pluginSelectors, which contains a series of selector functions used to retrieve data from the plugin storage state. Selectors are functions that extract and derive data from a zustand store. This specific example is for managing the state related to the frontend application's plugin system.

Here are some key points to note:

  • getCustomPluginById: Returns the custom plugin based on the plugin ID.
  • getInstalledPluginById: Returns the installed plugin based on the plugin ID.
  • getPluginMetaById: Returns the plugin metadata based on the plugin ID.
  • getPluginSettingsById: Returns the plugin settings based on the plugin ID.
  • getToolManifestById: Returns the plugin manifest based on the plugin ID.
  • installedCustomPluginMetaList: Returns metadata for all installed custom plugins.
  • installedPluginManifestList: Returns the manifests of all installed plugins.
  • installedPluginMetaList: Returns a processed metadata list for all installed plugins.
  • installedPlugins: Returns the list of all installed plugins.
  • isPluginHasUI: Determines if the plugin has UI components based on the plugin ID.
  • isPluginInstalled: Checks if the plugin with the given ID is installed.
  • storeAndInstallPluginsIdList: Returns the identifiers of all installed plugins.

Selectors are highly modular and maintainable. By encapsulating complex state selection logic in separate functions, they make the code more concise and intuitive when accessing state data in other parts of the application. Additionally, by using TypeScript, each function can have clear input and output types, which helps improve code reliability and development efficiency.

Taking the installedPluginMetaList method as an example, its code is as follows:

ts
const installedPlugins = (s: ToolStoreState) => s.installedPlugins;

const installedPluginMetaList = (s: ToolStoreState) =>
  installedPlugins(s)
    // Filter out Composio plugins (they have their own display location)
    .filter((p) => !p.customParams?.composio)
    .filter((plugin) => isInstalledPluginAvailableInCurrentEnv(plugin))
    .map<InstallPluginMeta>((p) => ({
      author: p.manifest?.author,
      createdAt: p.manifest?.createdAt,
      homepage: p.manifest?.homepage,
      identifier: p.identifier,
      runtimeType: p.runtimeType,
      type: p.source || p.type,
      ...getPluginMetaById(p.identifier)(s),
    }));
  • installedPlugins selector: Used to retrieve the raw list of all installed plugins from the tool state storage ToolStoreState.
  • installedPluginMetaList selector: Calls the installedPlugins selector, filters out Composio plugins and plugins unavailable in the current environment, and maps the remaining entries into the metadata shape consumed by the UI.

In components, the final consumed data can be directly obtained by importing:

tsx
import { useToolStore } from '@/store/tool';
import { pluginSelectors } from '@/store/tool/selectors';

const Render = () => {
  const list = useToolStore(pluginSelectors.installedPluginMetaList);

  return <> ...
</>;
};

The benefits of implementing this approach are:

  1. Decoupling and reusability: By separating selectors from components, we can reuse these selectors across multiple components without rewriting data retrieval logic. This reduces duplicate code, improves development efficiency, and makes the codebase cleaner and easier to maintain.
  2. Performance optimization: Selectors can be used to compute derived data, avoiding redundant calculations in each component. When the state changes, only the selectors dependent on that part of the state will recalculate, reducing unnecessary rendering and computation.
  3. Ease of testing: Selectors are pure functions, relying only on the passed parameters. This means they can be tested in an isolated environment without the need to simulate the entire store or component tree.
  4. Type safety: As LobeHub uses TypeScript, each selector has explicit input and output type definitions. This provides developers with the advantage of auto-completion and compile-time checks, reducing runtime errors.
  5. Maintainability: Selectors centralize the logic for reading state, making it more intuitive to track state changes and management. If the state structure changes, only the relevant selectors need to be updated, rather than searching and replacing in multiple places throughout the codebase.
  6. Composability: Selectors can be composed with other selectors to create more complex selection logic. This pattern allows developers to build a hierarchy of selectors, making state selection more flexible and powerful.
  7. Simplified component logic: Components do not need to know the structure of the state or how to retrieve and compute the required data. Components only need to call selectors to obtain the data needed for rendering, simplifying and clarifying component logic.

With this design, LobeHub developers can focus more on building the user interface and business logic without worrying about the details of data retrieval and processing. This pattern also provides better adaptability and scalability for potential future changes in state structure.