PostReplicateAdd() and PostReplicatedChange() are never called on the client in UE5 multiplayer
05:55 10 Nov 2025

I’m starting to work with Unreal Engine 5 multiplayer and I’m trying to serialize my equipment items to send them back to the client using RPC. However, I’ve noticed that the client’s PostReplicateAdd() and PostReplicatedChange() functions are never being called.

In other words, the replicated array (or struct) doesn’t seem to trigger replication callbacks on the client side.

Here’s the relevant part of my class code:

EquipmentComponent.h

USTRUCT(BlueprintType)
struct FRPGEquipmentEntry : public FFastArraySerializerItem
{
    GENERATED_USTRUCT_BODY()

    UPROPERTY(BlueprintReadOnly)
    FGameplayTag EntryTag = FGameplayTag();

    UPROPERTY(BlueprintReadOnly)
    FGameplayTag RarityTag = FGameplayTag();

private:

    UPROPERTY()
    TObjectPtr Instance = nullptr;
};

DECLARE_MULTICAST_DELEGATE_OneParam(FOnEquipmentEntrySignature, const FRPGEquipmentEntry& /*Equipment Entry*/);

USTRUCT()
struct FRPGEquipmentList : public FFastArraySerializer
{
    GENERATED_USTRUCT_BODY()

    FRPGEquipmentList() :
    OwnerComponent(nullptr)
    {}

    FRPGEquipmentList(UEquipmentManagerComponent* InComponent) :
    OwnerComponent(InComponent)
    {}

    URPGAbilitySystemComponent* GetAbilitySystemComponent();

    UEquipmentInstance* AddEntry(const TSubclassOf& InEquipmentDefinition, const TArray& StatEffects);
    void RemoveEntry(UEquipmentInstance* InEquipmentInstance);

    // FFastArraySerializer Contract
    void PreReplicatedRemove(const TArrayView RemovedIndices, int32 FinalSize);
    void PostReplicateAdd(const TArrayView AddedIndices, int32 FinalSize);
    void PostReplicatedChange(const TArrayView ChangedIndices, int32 FinalSize);

    bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams)
    {
       return FastArrayDeltaSerialize(Entries, DeltaParams, *this);
    }

    FOnEquipmentEntrySignature EquipmentEntryDelegate;
    FOnUnEquippedEntry UnEquippedEntryDelegate;
    
    
private:
    UPROPERTY(NotReplicated)
    TObjectPtr OwnerComponent;
    
    UPROPERTY()
    TArray Entries;
};

template<>
struct TStructOpsTypeTraits : TStructOpsTypeTraitsBase2
{
    enum
    {
       WithNetDeltaSerializer = true
    };
};
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class RPGSYSTEMS_API UEquipmentManagerComponent : public UActorComponent
{
    GENERATED_BODY()

public: 

    UPROPERTY(Replicated)
    FRPGEquipmentList EquipmentList;
EquipmentComponent.cpp

UEquipmentManagerComponent::UEquipmentManagerComponent() :
    EquipmentList(this)
{
    PrimaryComponentTick.bCanEverTick = true;
    SetIsReplicatedByDefault(true);
}

void UEquipmentManagerComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(UEquipmentManagerComponent, EquipmentList);
}
UEquipmentInstance* FRPGEquipmentList::AddEntry(const TSubclassOf& InEquipmentDefinition,
                                                const TArray& StatEffects)
{
    check(InEquipmentDefinition);
    check(OwnerComponent);
    check(OwnerComponent->GetOwner()->HasAuthority());

    const UEquipmentDefinition* EquipmentCTO = GetDefault(InEquipmentDefinition);
    TSubclassOf InstanceType = EquipmentCTO->InstanceType;

    if (!IsValid(InstanceType))
    {
       InstanceType = UEquipmentInstance::StaticClass();
    }

    for (auto EntryIt = Entries.CreateIterator(); EntryIt; ++EntryIt)
    {
       FRPGEquipmentEntry& Entry = *EntryIt;

       if (Entry.SlotTag.MatchesTagExact(EquipmentCTO->SlotTag))
       {
          RemoveEntry(Entry.Instance);
          break;
       }
    }

    FRPGEquipmentEntry& NewEntry = Entries.AddDefaulted_GetRef();
    NewEntry.EntryTag = EquipmentCTO->ItemTag;
    NewEntry.RarityTag = EquipmentCTO->RarityTag;
    NewEntry.SlotTag = EquipmentCTO->SlotTag;
    NewEntry.Instance = NewObject(OwnerComponent->GetOwner(), InstanceType);
     
    MarkItemDirty(NewEntry);
    
    EquipmentEntryDelegate.Broadcast(NewEntry);

    
    return NewEntry.Instance;
}
void FRPGEquipmentList::PostReplicateAdd(const TArrayView AddedIndices, int32 FinalSize)
{
    for (const int32 Index : AddedIndices)
    {
       const FRPGEquipmentEntry& Entry = Entries[Index];
       
       EquipmentEntryDelegate.Broadcast(Entry);
       
       GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red,
          FString::Printf(TEXT("Equipped Item: %s"), *Entry.EntryTag.ToString()));
    }
}

this component is attached to PlayerController

ARPGPlayerController::ARPGPlayerController()
{
    bReplicates = true;

    EquipmentComponent = CreateDefaultSubobject(TEXT("EquipmentManagerComponent"));
    EquipmentComponent->SetIsReplicated(true);
}
void ARPGPlayerController::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME(ARPGPlayerController, EquipmentComponent);
}
network-programming unreal-engine5 unreal