From R Shiny and Angular to React SPA: A Deep Dive into Architecture, State, and Performance

Judy
June 16, 2025
5 mins read
From R Shiny and Angular to React SPA: A Deep Dive into Architecture, State, and Performance

🛠 From R Shiny and Angular to React SPA: A Deep Dive into Architecture, State, and Performance

In our previous case study, we shared how we unified RCUG’s legacy R Shiny and Angular apps into a single React-based SPA.

This post takes you deeper into the architecture and engineering practices that made that possible—how we modernized, modularized, and mobilized a critical scientific platform.


📄 Background

As the Atware team, we partnered with Dr. Peter Houk—marine ecologist at the University of Guam Marine Lab and lead of the Micronesia Coral Reef Monitoring Network—to modernize a fragmented digital platform. The system previously relied on R Shiny for data visualization and Angular for data entry.

This portal supports the Micronesia Challenge, a regional conservation effort focused on tracking reef health and the effectiveness of Marine Protected Areas (MPAs). Our modernization aimed to unify data workflows, improve usability, and offer real-time scientific insight across communities and ecosystems.


🧱 The Legacy Stack & Why It Needed Change

The transition to a unified platform was driven by the need to resolve key limitations of the previous system, including disconnected workflows, redundant logic, and limited interactivity. Our rationale for choosing React—based on its modular architecture, performance benefits, and strong ecosystem—is covered in the original case study.


⚛️ Modular Components with React

React's component-based structure allowed us to abstract and reuse functionality across the portal. Charts, filters, forms, and even layout elements were broken down into modular blocks, making the system easier to scale and maintain.

import { Card, CardContent, CardHeader, CardTitle } from "./card";

export const ChartSection = ({ title, targetData, charts }) => (
  <Card className="w-full">
    <CardHeader>
      <CardTitle className="text-rose-600">{title}</CardTitle>
    </CardHeader>
    <CardContent>{charts(targetData)}</CardContent>
  </Card>
);

Example usage:

<ChartSection
  targetData={targetSite}
  charts={(site) => <CoralPerSite site={site} island={island} />}
  title="Coral Distributions"
/>

📝 Smarter Forms: Angular → React + Zod

We use react-hook-form with zod to build dynamic, validated survey forms. Here’s a simplified example of a fish survey event entry:

const schema = z.object({
  site: z.number(),
  observer: z.string().min(1),
  ...
});

const form = useForm({ resolver: zodResolver(schema) });

Sample Form UI:

import { Controller } from "react-hook-form";

<form className="flex flex-col gap-4">
  <Controller
    control={form.control}
    name="site"
    render={({ field }) => (
      <SiteSelect selectedSite={field.value} onSiteChange={field.onChange} />
    )}
  />

  <Input {...form.register("observer")} placeholder="Observer name" />
</form>;

🔌 React Query for Data Fetching

We used React Query to manage server state in a consistent, declarative way—handling caching, refetching, and background updates automatically. This made the app more responsive and reduced unnecessary API calls.

To keep our components clean, we encapsulated query logic into custom hooks, such as useIslands.

import { useQuery } from "@tanstack/react-query";

export function useIslands() {
  return useQuery({
    queryKey: ["islands"],
    staleTime: Infinity,
    queryFn: async () => {
      const [islands, sites] = await Promise.all([
        getIslands(),
        getSiteMetadata(),
      ]);
      // Mapping logic omitted
    },
  });
}

Example usage:

const { data: islands } = useIslands();

Thanks to React Query’s cache, once this data is loaded, it remains available across the session without triggering additional network requests—making navigation between pages and components feel instant.

We also used useMutation for operations like submitting new records. It handles loading, error, and success states gracefully, and allows for automatic refetching or optimistic updates when needed.

🔗 Learn more about useMutation in the React Query documentation


📊 Powerful Visualizations with ECharts

We replaced R Shiny’s ggplot-based charts with interactive, embeddable visualizations using Apache ECharts.

To streamline chart rendering across the portal, we built a lightweight wrapper around ECharts. This lets us pass in configuration options to render different charts without repeating setup code.

Chart wrapper component

import { useEffect, useRef } from "react";
import { echarts, ECOption } from "@/components/ui/echart";

export const ChartWrapper = ({
  option,
  width = "100%",
  height = "100%",
}: {
  option: ECOption;
  width?: string | number;
  height?: string | number;
}) => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!ref.current) return;
    const chart = echarts.init(ref.current);
    chart.setOption(option);
    return () => chart.dispose();
  }, [option]);

  return <div ref={ref} style={{ width, height }} />;
};

This minimal wrapper handles ECharts setup, rendering, and cleanup. It can be reused throughout the app by simply passing in an option object.


📱 Responsive Design with Tailwind CSS

We used Tailwind CSS to build responsive, consistent layouts that adapt well to tablets, mobile devices, and varying network conditions. This was especially important for our end users—conservation teams working in remote island regions—who need a fast and usable interface in the field.

Tailwind also helped us enforce a clean design system across the entire portal with utility-first classes, making components easier to read and maintain.

<div className="grid gap-4 md:grid-cols-2">
  <div className="rounded border p-4 shadow">
    <h2 className="mb-2 text-base font-semibold">Inverts Distribution</h2>
    <div className="flex h-64 items-center justify-center">
      {hasData ? <InvertsChart data={data} /> : <NoDataForChart />}
    </div>
  </div>
</div>

🔄 Backend Logic: R Shiny → Python (Pandas) + Django

We also migrated the data processing logic from the previous R Shiny frontend to the Django backend. More details are available here


✅ Results & Outcomes

  • Unified workflows in a single React SPA
  • Real-time interaction and faster load times
  • Visualizations that engage and inform communities
  • Easier code maintenance and team collaboration
  • A flexible platform to evaluate conservation actions and climate impacts

🌊 Final Thoughts

By replacing legacy components with a modern React stack, we delivered a responsive, performant, and extensible platform that empowers Micronesia’s reef conservation work.

📚 Read the full backstory of Micronesia Reef Monitoring.

🚀 Ready to modernize your data-driven platform? Let’s build something together.