Merging Props

Combine new data with existing props for infinite scroll and live updates.

See also

Official Inertia.js docs: Merging Props

The merge() Helper

Use merge() to append or prepend data instead of replacing:

from litestar_vite.inertia import merge

@get("/posts", component="Posts")
async def list_posts(page: int = 1) -> dict:
    posts = await Post.paginate(page=page, per_page=20)
    return {
        "posts": merge("posts", posts.items),  # Append to existing
    }

Merge Strategies

# Append new items to end (default)
merge("posts", new_posts)
merge("posts", new_posts, strategy="append")

# Prepend new items to beginning
merge("messages", new_messages, strategy="prepend")

# Deep merge nested objects
merge("settings", updated_settings, strategy="deep")

Match On Key

Update existing items instead of duplicating:

# Match items by ID - updates existing, appends new
merge("posts", updated_posts, match_on="id")

# Match on multiple keys
merge("items", items, match_on=["type", "id"])

Infinite Scroll

Complete infinite scroll example:

Backend

from litestar_vite.inertia import merge, scroll_props

@get("/posts", component="Posts")
async def list_posts(page: int = 1) -> dict:
    posts = await Post.paginate(page=page, per_page=20)
    return {
        "posts": merge("posts", posts.items),
        "pagination": scroll_props(
            page_name="page",
            current_page=page,
            previous_page=page - 1 if page > 1 else None,
            next_page=page + 1 if posts.has_next else None,
        ),
    }

Frontend

import { usePage, router, WhenVisible } from "@inertiajs/react";

interface Props {
  posts: Post[];
  pagination: {
    nextPageUrl?: string;
  };
}

export default function Posts({ posts, pagination }: Props) {
  return (
    <div>
      {posts.map((post) => (
        <PostCard key={post.id} post={post} />
      ))}

      {pagination.nextPageUrl && (
        <WhenVisible
          always
          params={{ only: ["posts"], data: { page: currentPage + 1 } }}
        >
          <Spinner />
        </WhenVisible>
      )}
    </div>
  );
}
<script setup>
import { router, WhenVisible } from "@inertiajs/vue3";

const props = defineProps<{
  posts: Post[];
  pagination: { nextPageUrl?: string };
}>();
</script>

<template>
  <div>
    <PostCard v-for="post in posts" :key="post.id" :post="post" />

    <WhenVisible
      v-if="pagination.nextPageUrl"
      always
      :params="{ only: ['posts'], data: { page: currentPage + 1 } }"
    >
      <Spinner />
    </WhenVisible>
  </div>
</template>

scroll_props() Helper

Create pagination metadata for infinite scroll:

from litestar_vite.inertia import scroll_props

scroll_props(
    page_name="page",        # Query parameter name
    current_page=1,          # Current page number
    previous_page=None,      # None if at first page
    next_page=2,             # None if at last page
)

Automatic Pagination

Return pagination objects directly - items and scroll props are extracted:

from litestar.pagination import OffsetPagination

@get("/posts", component="Posts", infinite_scroll=True)
async def list_posts(offset: int = 0, limit: int = 20) -> OffsetPagination:
    posts, total = await Post.paginate(offset, limit)
    return OffsetPagination(items=posts, offset=offset, limit=limit, total=total)

The infinite_scroll=True opt enables automatic scroll_props extraction.

Supported pagination types:

  • litestar.pagination.OffsetPagination - items, limit, offset, total

  • litestar.pagination.ClassicPagination - items, page_size, current_page, total_pages

  • advanced_alchemy.service.OffsetPagination - same as Litestar OffsetPagination

  • Any object with an items attribute and pagination metadata

The pagination container is automatically unwrapped:

  1. items attribute is extracted and used as the prop value

  2. If infinite_scroll=True on the route, scroll_props is calculated from pagination metadata

  3. Prop key defaults to "items" but can be customized with key opt

@get("/posts", component="Posts", infinite_scroll=True, key="posts")
async def list_posts(offset: int = 0) -> OffsetPagination:
    ...  # Props will have "posts" instead of "items"

Protocol Response

Merge props are indicated in the response:

{
  "component": "Posts",
  "props": {"posts": ["Post 1", "Post 2"]},
  "mergeProps": ["posts"],
  "scrollRegion": {
    "pageName": "page",
    "currentPage": 1,
    "nextPageUrl": "/posts?page=2"
  }
}

See Also