CodeApps BestPractices Accessing external Data

CodeApps Best Practices & Patterns

How to manage external data access and side effects, explained for CodeApps devs, not just React experts 

Introduction

Since Code Apps are natively part of the Microsoft ecosystem, they are most frequently paired with Dataverse as a primary data source. However, there are scenarios where you must interface directly with external data sources or APIs. Handling these external integrations introduces critical side effects such as: triggering API requests, fetching vital data payloads, and explicitly closing connections. Without proper patterns, managing resource fetching, disposal, and cleanup in Code Apps quickly becomes a pain point, leading to memory leaks and fragile code. In this article, I will share production-ready techniques to help you precisely manage these side effects. 

A Little Catch-Up & Finding Commonalities

Under the hood, CodeApps shares the exact same core architecture and framework (React) as Power Apps Component Framework (PCF). The main difference is that PCF provides these foundational capabilities pre-configured, whereas in CodeApps, we need to construct them ourselves. Among these native capabilities are State management and Hooks. For example, on PCF projects the context object, which exposes all form properties globally to the component, uses State management and Hooks techniques to do so. We will leverage these two structural patterns to seamlessly manage external data access and handle side effects. 

What Are State and Hooks ?

State is a foundational concept in the React ecosystem. It enables your components to remain dynamic, self-contained, and responsive to the user data they interact with. Think of it this way: React apps and CodeApps are essentially static pages at their core. State is the mechanism that allows users to interact with data while reflecting those updates across the UI in real time. 

Before Hooks were introduced, managing State and tracking data updates required class based components, which were often verbose and complex to maintain. Hooks transformed this paradigm. They provide a modern, streamlined approach to managing state directly inside components – allowing you to inspect, retrieve, and update internal component data using a simple getter and setter pattern. Among these Hooks, useEffect is the key mechanism we need to implement in CodeApps to reliably access and manage external data. 

What Is the useEffect Hook and When Should You Use It ? 

The useEffect Hook is the primary mechanism that allows your component to execute side effects and interact with systems outside the immediate React/CodeApps render cycle. In practice, it provides a structured way to handle operations that need to occur outside the pure rendering of UI components, such as:  

  • Fetching data from an external API (including the Dataverse Web API)
  • Retrieving user profile information or session data
  • Initializing timers, intervals, or event listeners
  • Direct DOM manipulations when specialized browser access is required ( trust me, sometimes you need it)

Think of a “side effect” as an operation that needs to execute in response to lifecycle events or state updates, such as when a component mounts, or when specific reactive data dependencies change. The code inside useEffect ensures your application can safely synchronize with external data sources and render the most up to date information without interrupting or blocking the whole UI lifecycle. 

An Example of useEffect

The useEffect Hook accepts two arguments:

  1. A callback function: This function contains the code you want to run as a side effect (i.e., the logic that should execute when your component loads or when its dependencies update).
  2. A dependency array: This array specifies the exact variables or objects that trigger the callback function. Whenever a value inside this array changes, the effect re-runs. If you want your API or external data source to be called whenever a specific variable changes, you must include that variable in this array.
import React, { useEffect } from 'react';

const Component: React.FC = () => {
  const data: { value: unknown } = { value: null };

  useEffect(() => {
    // Your code here:
    // 1. Authenticate with your API
    // 2. Build your data object with the response
  }, []);

  return (
    // Your TSX here
  );
};

In the example above, the second argument is an empty dependency array ([]). This instructs the hook to execute the side effect only once: specifically when the component first loads (e.g., upon initial page load). 

Accessing external data in Practice

Let’s walk through a concrete scenario: suppose we are building a CodeApps control and need to display data from an external API (such as an Azure Function) inside a read-only grid. Additionally, this grid must update whenever the user clicks a “Refresh” button.

The “Refresh” button serves as the user interaction mechanism. To instruct the app to fetch new data, we must link that button click to a state variable and pass that variable into the dependency array (the second argument) of our useEffect Hook. Whenever the button is clicked, the variable updates, triggering useEffect to execute our data-fetching logic and call the Azure Function again.

Here is the updated pattern implementation structure: 

import React, { useState, useEffect } from 'react';

function CodeAppsReadOnlyComponentGrid() {
  const [data, setData] = useState<Record<string, unknown>[] | null>(null);
  const [refreshTrigger, setRefreshTrigger] = useState<number>(0);

  useEffect(() => {
    const controller = new AbortController();

    const fetchData = async (): Promise<void> => {
      try {
        const response = await fetch('https://your-azure-function-url', {
          signal: controller.signal
        });
        const result: Record<string, unknown>[] = await response.json();
        setData(result);
      } catch (error) {
        if (error instanceof Error && error.name !== 'AbortError') {
          console.error('Fetch failed:', error);
        }
      }
    };

    fetchData();

    // Cleanup: runs before the next effect execution, and on unmount
    return () => {
      controller.abort();
    };
  }, [refreshTrigger]);

  const handleRefresh = (): void => {
    setRefreshTrigger(prev => prev + 1);
  };

  return (
    <div>
      <button onClick={handleRefresh}>Refresh Data</button>
      {data && data.length > 0 ? (
        <table>
          {/* Render table headers and data rows here */}
        </table>
      ) : (
        <p>No results found</p>
      )}
    </div>
  );
}

Walking Through the Code, Step by Step

Since this tutorial is not only for experts, let’s break down exactly what’s happening in this pattern piece by piece.

The two state variables

const [data, setData] = useState<Record<string, unknown>[] | null>(null);
const [refreshTrigger, setRefreshTrigger] = useState<number>(0);

useState gives you a pair: the current value, and a function to update it. data starts as null, since there’s nothing to display yet, and is typed as an array of records or null. refreshTrigger starts at 0 and works as a simple counter. Its actual value doesn’t matter, it only exists to signal that a refresh happened. 

The AbortController

const controller = new AbortController();

This is a native browser object built for cancelling a network call in progress . Think of it as a switch: as long as you do not flip it, the fetch/retrieve runs normally.

The fetchData function

The fetchData function is self-explanatory: it is where you place the code that retrieves data from an API or external data source. Immediately following its definition, the function is invoked directly inside the Hook.

fetchData();

This is what actually fires the network call every time the effect/hook runs. 

The cleanup itself

return () => {
  controller.abort();
};

The useEffect runs automatically in two cases: right before the effect runs again (so every time refreshTrigger changes, button clicked), and when the component unmounts. It flips the switch (controller.abort()), cancelling the previous fetch if it’s still running. This is exactly what prevents an older slower fetch from overwriting the results of a newer one.

The refresh trigger

const handleRefresh = (): void => {
  setRefreshTrigger(prev => prev + 1);
};

Every click increments refreshTrigger by 1. That is enough to re-run the effect/hook, since it counts as a change and it’s listed in the dependency array ( second argument of the useEffect function).

The render

<button onClick={handleRefresh}>Refresh Data</button>
{data && data.length > 0 ? (
  <table>...</table>
) : (
  <p>No results found</p>
)}

The button calls handleRefresh. Below it, it is a classical condition: if data exists and has items, then render the table, otherwise show “No results found”.

Conclusion

Managing external data in CodeApps is not just about fetching it, it’s about fetching it correctly, and only proven patterns like useEffect actually get you there. The cleanup function has its value: it is what keeps your app from leaking memory and serving outdated data when a user clicks around faster than your API can respond.

These are not isolated cases. Any CodeApps control fetching data from an external API or Azure Function( in our case ) will eventually encounter these issues – typically in production and under conditions that make them difficult to detect. By embedding proper cleanup patterns into your architecture from the start, you eliminate entire classes of runtime bugs. 

If you’re building CodeApps components that touch external data or timer control then this pattern should belong in your toolkit from day one.

Leave a Comment

Your email address will not be published. Required fields are marked *