ViewComponent/view_component · error · ArgumentError

Cannot deserialize unknown component: #{hash["component_clas

Error message

Cannot deserialize unknown component: #{hash["component_class"]}

What it means

Proxy.deserialize rebuilds a deferred component by calling safe_constantize on the stored component_class string from the serialized hash. If that class name no longer resolves — renamed, removed, namespaced differently, or not loadable in the worker process — safe_constantize returns nil and deserialize raises ArgumentError ('Cannot deserialize unknown component: <name>'). This typically surfaces inside a background job or Turbo Stream delivery after a deploy.

Source

Thrown at lib/view_component/serializable/proxy.rb:14

# frozen_string_literal: true

module ViewComponent
  module Serializable
    # A proxy that wraps a component class and its initialization arguments, deferring
    # component instantiation until render time. This allows slot calls and other
    # post-initialize configuration to be captured and replayed, and enables the
    # proxy itself (rather than a live component instance) to be serialized for
    # background jobs (e.g. ActiveJob / Turbo Streams).
    class Proxy
      # Rebuilds a Proxy from a serialized hash produced by +serialize+
      def self.deserialize(hash)
        klass = hash["component_class"].safe_constantize
        raise ArgumentError, "Cannot deserialize unknown component: #{hash["component_class"]}" unless klass

        args = ActiveJob::Arguments.deserialize(hash["initialize_args"] || [])
        proxy = new(klass, *args)

        Array(hash["slot_calls"]).each do |call|
          method_name = call["method"].to_sym
          slot_args = ActiveJob::Arguments.deserialize(call["args"])
          proxy.public_send(method_name, *slot_args)
        end

        proxy
      end

      attr_reader :component_class, :initialize_args, :slot_calls

      def initialize(component_class, *args)
        if component_class.name.nil?
          raise UnserializableError, "Cannot serialize anonymous component class #{component_class.inspect}"

View on GitHub (pinned to 9f22c36fa7)

Solutions

  1. Restore or alias the class name so safe_constantize resolves again (e.g., keep a temporary constant alias during migration).
  2. Drain or discard stale queued jobs that reference the old class name before/after the rename.
  3. Verify the class loads in the worker process: 'User::CardComponent'.safe_constantize should not be nil in the job runner's environment.
  4. Wrap deserialization of stale payloads with a rescue and dead-letter or discard the job (see defense strategy).

Example fix

# before
# job payload enqueued pre-rename: {"component_class" => "User::CardComponent", ...}
ViewComponent::Serializable::Proxy.deserialize(payload)
# -> ArgumentError: Cannot deserialize unknown component: User::CardComponent

# after
# renamed component gets a temporary alias during migration
class CardComponent < ViewComponent::Base; end
User::CardComponent = CardComponent # remove after old jobs drain
Defensive patterns

Strategy: try-catch

Validate before calling

hash = {"component_class" => "User::CardComponent", "initialize_args" => [], "slot_calls" => []}
return if hash["component_class"]&.safe_constantize.nil? # drop stale payload early

ViewComponent::Serializable::Proxy.deserialize(hash)

Try / catch

begin
  proxy = ViewComponent::Serializable::Proxy.deserialize(payload)
rescue ArgumentError => e
  raise unless e.message.start_with?("Cannot deserialize unknown component")
  Rails.logger.warn("discarding stale component job: #{e.message}")
  nil # caller skips enqueueing/rendering
end

Prevention

When it happens

Trigger: A serialized job payload referencing 'User::CardComponent' after the component was renamed to 'CardComponent'; enqueuing in one deploy and executing in another where the class was deleted; a job worker with different autoload/eager-load configuration where the constant is unavailable; a manually built hash with a typo in component_class.

Common situations: Renaming/refactoring component classes while old render_later jobs are still queued; long-lived queues (Sidekiq, DelayedJob) spanning deploys; zeitwerk namespace changes after moving files; multi-process setups where one environment lacks the component.

Related errors


AI-assisted analysis of ViewComponent/view_component@9f22c36fa7 (2026-08-23). Data as JSON: /api/errors/1609c36abf1e11ea. Report an issue: GitHub.