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 typing import Any

from litestar import get
from litestar.params import FromQuery
from litestar_vite.inertia import merge

@get("/posts", component="Posts")
async def list_posts(page: FromQuery[int] = 1) -> dict[str, Any]:
    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 typing import Any

from litestar import get
from litestar.params import FromQuery
from litestar_vite.inertia import merge, scroll_props

@get("/posts", component="Posts")
async def list_posts(page: FromQuery[int] = 1) -> dict[str, Any]:
    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 import get
from litestar.params import FromQuery
from litestar.pagination import OffsetPagination

@get("/posts", component="Posts", infinite_scroll=True)
async def list_posts(
    offset: FromQuery[int] = 0, limit: FromQuery[int] = 20
) -> OffsetPagination[Post]:
    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

from litestar import get
from litestar.params import FromQuery
from litestar.pagination import OffsetPagination

@get("/posts", component="Posts", infinite_scroll=True, key="posts")
async def list_posts(offset: FromQuery[int] = 0) -> OffsetPagination[Post]:
    ...  # 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"],
  "scrollProps": {
    "pageName": "page",
    "currentPage": 1,
    "nextPage": 2,
    "previousPage": null
  }
}

See Also