Skip to content

remove-memoization: useMemo is replaced by the factory function, not its result; import type and semicolon style are rewritten #10

Closed

Description

@rjolaverria

Summary

react-19-remove-memoization replaces useMemo(factory, deps) with the factory function instead of the factory's result, so every memoized value becomes a function. Two smaller problems in the import rewrite turn import type into a value import and add semicolons to semicolon-free files.

1. useMemo is replaced by its factory, not its result

replacementText returns the first argument's text for every call. That is right for useCallback(fn, deps) and memo(Component), but useMemo evaluates to what the factory returns.

Input:

const value = useMemo(() => ({ chat, sendUserMessage }), [chat, sendUserMessage])
const states = useMemo(() => [...uploads.values()], [uploads])
return <Ctx.Provider value={value}>{children}</Ctx.Provider>

Actual output:

const value = () => ({ chat, sendUserMessage })
const states = () => [...uploads.values()]

Expected output:

const value = { chat, sendUserMessage }
const states = [...uploads.values()]

Every consumer of the context now reads undefined, and states.some(...) throws because states is a function.

The tests/use-memo and tests/typescript-use-memo fixtures encode this: the input memoizes a boolean and expected.tsx asserts () => boolean.

The jscodeshift version in codemod/commons (react/19/remove-memoization) has the same defect: its transform is replaceWith(path.value.arguments[0]) for all three calls.

2. Untouched React imports are rewritten anyway

importStatementReplacement rebuilds every React import and compares the rebuilt text to the original. The rebuilt text never carries the clause-level type keyword and always ends in ;, so an import that loses nothing is still replaced:

import type { ReactNode } from 'react'     // before
import { ReactNode } from 'react';          // after

Under verbatimModuleSyntax that is a compile error (TS1484). In a semicolon-free codebase every touched file gains stray semicolons.

3. Unrelated unused React specifiers are deleted

The rebuild keeps only specifiers that are referenced somewhere in the file, so an unused ComponentProps on a type-only import is deleted even though the codemod never touched it. That goes beyond removing memoization.

Real-world impact

Running the codemod over 13 files in a React 19 + React Compiler app produced 18 TypeScript errors and 169 failing unit tests, all from (1), plus the import churn from (2).

Fix

I have a patch with corrected and new fixtures that I'll open as a PR against this issue. It replaces useMemo with the factory result (expression-bodied arrow → its body, block or async factory → IIFE, identifier → call), leaves imports that lose no specifier untouched, preserves the file's semicolon style, and only removes the memoization specifiers themselves.

Activity

  1. alexbit-codemod commented on Oct 30, 2026

    @alexbit-codemod
    Contributor

    thanks so much for reporting the issue and the fix.

  2. alexbit-codemod commented on Oct 31, 2026

    @alexbit-codemod
    Contributor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions