Mastering Rive Animation: A Complete Guide for React Developers

Published: (December 16, 2025 at 11:32 PM EST)
6 min read
Source: Dev.to

Source: Dev.to

Introduction

In modern web development, creating lively and exciting user experiences (UX) requires more than just simple CSS transitions. We need complex, interactive animations that look great but don’t slow down the app. This is why Rive has become a powerful “secret weapon” in our technology stack.

Today, let’s explore the full process of using Rive in our project, from understanding what it is to designing the architecture and implementing it with real source code.


Part 1: Rive Basics – From Design to Code

1. What is Rive? And How Do We Create It?

Rive is a complete ecosystem that includes a Design Tool (Editor) and a Runtime engine for code. It is different from Lottie (which just plays a JSON file) or video – Rive acts like a real interactive machine.

RiveFlow

The Workflow

Working with Rive is like combining Figma (for drawing) and After Effects (for movement), plus a bit of logic like Unity games.

PhaseWhat HappensTips
DesignDesigners use the Rive Editor on the web or desktop. They can draw directly in Rive or import items from Figma.Always name layers clearly (e.g., UserAvatar, ScoreText) so developers can find them easily in the code.
AnimateDesigners use a Timeline to create movements (e.g., Run, Jump, Idle). Rive uses “bones” (skeletal animation) for smooth character motion.
State Machine (Logic)Designers connect animations with logic. Example: Switch from Idle to Running when the input isRunning is true.Agree on input names (Triggers, Numbers, Booleans) so React can control the animation perfectly.

2. The Difference Between .REV and .RIV

ExtensionDescriptionUsage
.REVSource project file (uncompressed). Like a .PSD in Photoshop. Keep it safe for future edits. Do not ship this in your app.Design & animation iteration.
.RIVRuntime file (binary, optimized, tiny). Ready to run on the web. This is what you place in src/assets.Production bundle.

3. Where to Find Free Rive Files?

If you want to practice but don’t have a designer, check out the Rive Community – it’s “GitHub for animations.” Search for “Loading” or “Button,” click Remix to see how it was built, and export the .riv file for free.


Part 2: Technical Implementation in Our Architecture

1. Project Structure

Based on our current codebase, here’s how we organize things:

src/
├─ constants/
│   └─ rive.js               # Artboard names, state machines, input names (prevents typos)
├─ utils/
│   └─ riveUtils.js          # Helper functions to create configurations quickly
├─ hooks/
│   └─ useRiveAnimation.js   # “Heart” of the system – handles loading, updates, events
└─ view/
    └─ shared/.../Templates  # UI components (popups, banners, widgets) that use the hook

2. Deep Dive: The useRiveAnimation Hook

This hook solves the hard problems: loading states, error handling, and—most importantly—dynamic content updates (changing text or images inside the animation).

integratewithhook

How it works

The hook accepts a config object with src, artboard, and stateMachines.

// src/hooks/useRiveAnimation.js (simplified)
const { rive, RiveComponent } = useRive({
  src: finalSrc,
  artboard,
  stateMachines,
  autoplay,
  // …other options
});

Key Feature: Dynamic Updates

We can change text (e.g., a countdown timer) or images (e.g., a user avatar) inside a running animation file using updateMultipleContents. The hook supports several RiveFieldTypes:

RiveFieldTypeWhat It Does
StringChange text content
ImageSwap an image inside the animation
Trigger / Boolean / NumberControl logic/flow of the animation

The implementation batches updates with Promise.allSettled for optimal performance:

// Efficient batch updating
const updateMultipleContents = useCallback(
  async (updates, artboardPath) => {
    // …logic to locate the view model (vmi)…
    const promises = updates.map(({ path, type, value }) => {
      switch (type) {
        case RiveFieldType.String:
          return updateStringValue(vmi, path, value);
        case RiveFieldType.Image:
          return updateImageValue(vmi, path, value);
        // …handle other types…
      }
    });
    await Promise.allSettled(promises);
  },
  [rive]
);

3. Using the Hook in a Component

import { useRiveAnimation } from '@/hooks/useRiveAnimation';
import { RIVE_ASSETS } from '@/constants/rive';

export const AvatarBadge = ({ avatarUrl, userName }) => {
  const { RiveComponent, setInput, updateMultipleContents } = useRiveAnimation({
    src: RIVE_ASSETS.avatarBadge,
    artboard: 'Main',
    stateMachines: ['BadgeSM'],
    autoplay: true,
  });

  // Update avatar image and name when props change
  useEffect(() => {
    updateMultipleContents(
      [
        { path: 'Avatar/Image', type: RiveFieldType.Image, value: avatarUrl },
        { path: 'Name/Text', type: RiveFieldType.String, value: userName },
      ],
      'Main'
    );
  }, [avatarUrl, userName]);

  // Example: trigger a “pulse” animation on click
  const handleClick = () => setInput('PulseTrigger', true);

  return (
    <div onClick={handleClick}>
      <RiveComponent />
    </div>
  );
};

Recap

  • Rive gives us a design‑first, code‑ready animation workflow.
  • Keep .rev files for iteration; ship only .riv files.
  • Centralise constants, utils, and a custom useRiveAnimation hook to avoid duplication.
  • Use updateMultipleContents to change text, images, and state‑machine inputs on the fly, keeping the UI responsive and the codebase maintainable.

With this setup, designers can craft rich interactive experiences, and developers can integrate them cleanly and efficiently across the entire application.

rt 3: How to Implement (Step‑by‑Step Guide)

Let’s imagine we are building a Countdown Widget (similar to WidgetManager/Templates/RiveAnimationTemplate.jsx).

Guide

Step 1: Prepare Constants

Define the inputs that the designer created in the Rive file.

// src/constants/rive.js
export const WIDGET_STATE_MACHINE = "Widget SM";

export const RiveInputs = {
  WidgetIn: "Widget In",   // Trigger: Play appear animation
  WidgetHover: "Hover",     // Boolean: Is mouse hovering?
};

export const RiveFields = {
  Time: "Timer/Time",      // Path to the text object in Rive
};

Step 2: Use the Hook in Your Component

Connect the hook. Always check isLoaded before showing the component.

const WidgetRive = ({ endTime }) => {
  const {
    RiveComponent,
    isLoaded,
    updateMultipleContents,
    triggerStateMachineInput,
  } = useRiveAnimation({
    src: "assets/widget.riv",
    artboard: "Main",
    stateMachines: WIDGET_STATE_MACHINE,
    autoplay: false, // We will play it manually later
  });

  // Update the timer every second
  useEffect(() => {
    if (isLoaded && endTime) {
      const timeStr = calculateTimeLeft(endTime);
      // Send new text to Rive
      updateMultipleContents([
        new RivePropertyUpdate(
          RiveFields.Time,
          RiveFieldType.String,
          timeStr
        ),
      ]);
    }
  }, [isLoaded, endTime]);

  return (
    <>
      {!isLoaded && <Spinner />}
      {isLoaded && <RiveComponent />}
    </>
  );
};

Step 3: Handling Interactions

In our BannerManager, we handle user clicks and hovers easily:

const handleMouseEnter = () => {
  // Tell Rive the mouse is over the banner
  triggerStateMachineInput(
    WIDGET_STATE_MACHINE,
    RiveInputs.WidgetHover,
    true
  );
};

const handleMouseLeave = () => {
  triggerStateMachineInput(
    WIDGET_STATE_MACHINE,
    RiveInputs.WidgetHover,
    false
  );
};

Part 4: Best Practices & Tips

To keep your animations smooth (60 FPS) and bug‑free, follow these tips from our source code.

Caching View Models

Our hook caches the instance (vmiDataRef). This is crucial—if you update a timer every second without caching, Rive must search for the text object 60 times a minute, which causes lag.

Resource Load Guard

Don’t render the Rive component until the file is actually downloaded, especially in pop‑ups.

if (isError) {
  return <FallbackImage />;
}
return isLoaded ? <RiveComponent /> : <LoadingSpinner />;

Prevent “Flickering”

Rive may show the default text (“Text”) for a split second before your data loads. Fix it by:

  1. Setting autoplay: false initially.
  2. Waiting for isLoaded.
  3. Calling updateMultipleContents to set your data.
  4. Then calling play().

Handling Errors

Always have a fallback. If the .riv file fails to load (404), the isError variable in the hook becomes true. Use this to show a static image or a close button so the user isn’t stuck.

Conclusion

Rive is more than just a decoration tool. It separates the movement logic from the React code, allowing designers to create beautiful animations while developers focus on data handling.

By mastering the useRiveAnimation hook, you can build widgets, banners, and pop‑ups that feel premium and incredibly smooth. Happy coding!

Back to Blog

Related posts

Read more »

Drupal: Exploring Canvas (part 2)

Exploring Components In my previous post I got acquainted with the new module, and it got off to a promising start. In this post I’m going to explore component...