Quick answer: Replicated UObject references on a component don’t replicate the sub-object itself. Override ReplicateSubobjects in C++ (or use Replicated Sub-Objects List on UActorComponent in newer versions).

An InventoryComponent holds an array of UItem* UObjects. The references replicate but the items themselves are null on the client — sub-objects didn’t replicate.

References vs Sub-Objects

Marking a UObject pointer as Replicated means the pointer replicates — the engine looks up the target by NetGUID on the client. If the target is a sub-object Created via NewObject and not registered for replication, the client never receives its data — the pointer resolves to null.

C++ Override

bool UInventoryComponent::ReplicateSubobjects(UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags)
{
    bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
    for (UItem* Item : Items)
    {
        if (IsValid(Item))
            WroteSomething |= Channel->ReplicateSubobject(Item, *Bunch, *RepFlags);
    }
    return WroteSomething;
}

This tells the engine each frame which UObjects ride along on the component’s replication.

Newer Sub-Object API

Recent UE versions add AddReplicatedSubObject / RemoveReplicatedSubObject on UActorComponent — opt in once on Create, opt out on Destroy, no override needed.

The Item Class Itself

The UItem class needs bReplicates = true in its constructor (or set with SetIsReplicatedByDefault(true)). Without that, even a registered sub-object is treated as non-replicating.

Verifying

Server adds an item. Client’s inventory component shows the item with all properties intact, not a null entry. Modifying the item on server propagates.

“Replicated pointer + replicated sub-object are two separate things. You need both for clients to see the data.”

For Blueprint-only inventory, prefer a struct array over UObject sub-objects — structs replicate natively without the sub-object dance.