Using React Custom Hooks to Enable New Features Across Microfrontends

✨ Let’s talk about dependencies! ✨Sometimes, a customer will need to track items across different parts of FloQast, and one solution we had is to track the priority and sequential order of those items.

In this post, I’ll walk you through how we used React custom hooks to create a scalable, reusable, and cross-domain dependencies feature that spans multiple Microfrontends (MFEs) — each representing a distinct page of the app.


Introduction

During the Financial Close Process, users face numerous challenges:

  • Creating relationships between resources: Task A is “Blocked By” Task B.
  • Enforcing task completion order: Dependencies ensure linear workflows (e.g., Task B can’t be completed until Task A is completed).
  • Visualizing dependencies: Users can see all dependencies related to a resource and their statuses at a glance.
  • Cross-domain dependencies: Resources across domains (like Checklists and Reconciliations) can depend on one another’s completion status.

In this context, a domain refers to a distinct MFE (or page) within the application, each with its own MongoDB collection for data management.

Implementing dependencies introduced numerous technical approaches, each with their own respective tradeoffs. We created a dependencies module (Nanofrontend) that extends functionality to React MFEs, ensuring consistent dependency handling across workflows.

  • What are Nanofrontends? Nanofrontends provide a hosted mechanism for sharing core functionality across different clients without adding to the bundle size of Microfrontends. Another appropriate name for Nanofrontends would be hosted dependencies or evergreen dependencies. Exporting a component / hook from a Nanofrontend (NFE) allows engineers to import and use that component in frontend clients.

Project Summary

The goal was to create a dependencies feature that could:

  • Be reused across multiple domains (like Checklists or Reconciliations), with each domain corresponding to an MFE / page and its data.
  • Fetch and manage dependency data for resources in different domains using a shared hook.
  • Allow seamless integration of new MFEs with minimal overhead for each new client that wants to leverage dependencies.

We achieved this by building a dedicated Nanofrontend for dependency management and exposing a React custom hook that could be reused across domains. By designing the hook to work with any domain, extending the feature to new MFEs included importing and using shared hooks rather than creating redundant code and API calls from multiple clients. (If you aren’t familiar with Microfrontend clients, here’s another FQ Engineering blog post on that topic)


High-Level Architecture Overview

The dependencies module integrates with client MFEs and its dependencies_api. Here’s how it works:

  1. Dependencies Module (NFE): Exports React hooks that fetch and manage dependency data.
  2. Client MFEs: Emit events to the NFE for new resources and fetch dependencies using NFE hooks.
  3. dependencies_api: Handles CRUD operations, circular dependency checks, and MongoDB interactions.
Dependencies diagram

How Custom Hooks Allow Extendability Across Domains

The core of this solution is the useFetchBatchDependenciesData custom hook, which simplifies cross-domain dependency management by returning all of the dependencies and resources involved with those dependencies from a single function.

Key Features

  • Fetches dependencies for any resource using its documentId.
  • Returns data about related dependencies, including their status and domain.
  • Automatically re-fetches dependencies when triggered by events like updates or task completions.

Why Support for Different Domains is Crucial

Each domain (like Checklists or Reconciliations) is a distinct MFE / page with its own MongoDB collection and workflows. Dependencies often span multiple domains, so the hook provides a unified way to:

  1. Fetch dependencies for any resource, regardless of domain/client.
  2. Manage cross-domain dependencies consistently.

Centralizing dependency handling in the Nanofrontend ensures synchronization across all workflows and domains.


Hook Example in Action

Here’s how the checklist-client MFE uses the custom hook to fetch dependencies and react to updates (The recs-client has the same code implemented to leverage the hook / event-emitting):

useEffect(() => {
    const handleDependenciesUpdated = () => {
        setDependencyUpdateTrigger((prev) => prev + 1);
    };

    window.addEventListener('dependenciesUpdated', handleDependenciesUpdated);

    return () => {
        window.removeEventListener('dependenciesUpdated', handleDependenciesUpdated);
    };
}, []);

const resourceIds = useMemo(
    () => checklists.map((checklist) => checklist._id),
    [checklists]
);

const { data, isLoading, error } = useFetchBatchDependenciesData({
    resourceIds,
    canViewDependencies: userCanViewDependencies,
    trigger: dependencyUpdateTrigger,
});

Implementation of the Hook in the NFE

Here’s the implementation of the useFetchBatchDependenciesData hook from the dependencies module:

export default function useFetchBatchDependenciesData({
    resourceIds,
    canViewDependencies,
    trigger,
}) {
    const [responseState, setResponseState] = useState({
        dependencies: [],
        isLoading: false,
        error: null,
    });

    useEffect(() => {
        if (!resourceIds.length || !canViewDependencies) {
            setResponseState((prevState) => ({
                ...prevState,
                isLoading: false,
            }));
            return;
        }

        setResponseState((prevState) => ({
            ...prevState,
            isLoading: true,
        }));

        const fetchDependencies = async () => {
            try {
                const dependencies = await DependenciesAPI.getDependenciesBatch(resourceIds);
                setResponseState({
                    dependencies,
                    error: null,
                    isLoading: false,
                });
            } catch (err) {
                setResponseState({
                    error: err,
                    isLoading: false,
                });
            }
        };

        fetchDependencies();
    }, [resourceIds, canViewDependencies, trigger]);

    return {
        data: responseState.dependencies,
        isLoading: responseState.isLoading,
        error: responseState.error,
    };
}

Benefits of Using Hooks from the fq-dependencies Nanofrontend:

  1. Reusability: Abstracts fetching dependency data, making it easy for any MFE to integrate.
  2. Extendability: Adding a new domain (like recs-client) requires minimal work — just plug in the hook and premade components!
  3. Centralized Updates: Any changes to dependency handling are made in the Nanofrontend and propagated to all MFEs.
  4. Consistency: Ensures all MFEs display and manage dependencies the same way, creating a unified user experience.

Engineering + Customer Impact

When we extended the dependencies feature from checklist-client to recs-client, it took less than a sprint. Why? Because the reusable hook and centralized Nanofrontend logic handled the heavy lifting.
This approach allowed us to deliver new functionality faster, reduce code duplication, and ensure consistent dependency management across domains. How does that technical strategy affect the customer? – It means we’re able to rollout an existing feature to multiple requested parts of the app in an efficient “plug and play” approach, giving the customer what they want at a faster pace.


Reflection + Looking Ahead

Building the dependencies feature as a modular Nanofrontend wasn’t just a technical choice — it was a business decision.

By centralizing dependency management and exposing reusable hooks, we created a scalable system that integrates workflows across various clients / parts of the app. The ability to fetch, display, and react to dependency updates with hooks and events transformed how we handle cross-domain workflows.

And the best part? When new domains pop up, they can quickly integrate into this ecosystem, allowing dependencies to be a feature that can be implemented into new FloQast MFEs with just a few lines of code.

Jackson Boehman

Jackson is a Senior Software Engineer at FloQast who loves to design and implement product-oriented full stack features. Outside of work, his hobbies include basketball, golf, reading and spending time with his corgi.



Back to Blog