Skip to content

map: Data-last sortBy in pipe infers single element type instead of array when input type flows through NoInfer #1364

Description

@neefrehman

Description

When using R.sortBy (data-first or data-last), TypeScript fails to resolve the input type when it receives a Mapped<T, K> (the return type of R.map) where T originates from TypeScript's built-in NoInfer<T>.

The Mapped type ({ -readonly [P in keyof T]: K }) works fine for standard array methods (.find(), .map()), but R.sortBy's T extends IterableContainer generic constraint can't unify Mapped<NoInfer<...>, K> with IterableContainer.

Minimal reproduction

import * as R from "remeda";

declare function getData<TData>(options: {
  select: (raw: { items: Array<{ type: string; name: string }> }) => TData;
}): { data: NoInfer<TData> | undefined };

const { data } = getData({
  select: (raw) => raw.items,
});

// ✅ R.map result works fine on its own — Mapped<T, K> resolves for .find()
const mapped = R.map(data ?? [], (item) => ({ ...item, extra: true }));
mapped.find((r) => r.type === "a"); // OK

// ❌ Passing that same Mapped<T, K> into R.sortBy fails
const sorted = R.sortBy(mapped, (item) => item.name);
sorted.find((r) => r.type === "a"); // Error: This expression is not callable

// ❌ R.pipe + data-last also fails
const piped = R.pipe(
  data ?? [],
  R.map((item) => ({ ...item, extra: true })),
  R.sortBy((item) => item.name),
);
piped.find((r) => r.type === "a"); // Error: This expression is not callable

// ✅ Native .map result (plain Array<K>) works with R.sortBy
const nativeMapped = (data ?? []).map((item) => ({ ...item, extra: true }));
const sorted2 = R.sortBy(nativeMapped, (item) => item.name);
sorted2.find((r) => r.type === "a"); // OK

// ✅ Explicit Array annotation on the intermediate also works
const explicit: Array<{ type: string; name: string; extra: boolean }> =
  R.map(data ?? [], (item) => ({ ...item, extra: true }));
const sorted3 = R.sortBy(explicit, (item) => item.name);
sorted3.find((r) => r.type === "a"); // OK

Context

This became an issue after @tanstack/react-query v5.100.13 switched from a custom NoInfer implementation to TypeScript's built-in NoInfer intrinsic in TanStack/query#10593. useQuery's return type wraps TData in NoInfer, so any data flowing through R.mapR.sortBy breaks.

Workarounds

  1. Use native .map() before R.sortBy (returns plain Array<K> instead of Mapped<T, K>)
  2. Store R.map result with an explicit Array<...> type annotation

Environment

  • remeda: 2.37.0
  • TypeScript: 5.9.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions