From 4a64b9986f99cbfeb22d2d6dee016b500797c11d Mon Sep 17 00:00:00 2001 From: Mohamad Mohebifar Date: Mon, 11 Nov 2024 11:43:34 -0500 Subject: [PATCH] [React19/replace-use-form-state] Fix import updates (#1382) --- .../19/replace-use-form-state/.codemodrc.json | 2 +- .../react/19/replace-use-form-state/README.md | 6 +-- .../__testfixtures__/fixture7.input.js | 10 ++++ .../__testfixtures__/fixture7.output.js | 9 ++++ .../__testfixtures__/fixture8.input.js | 6 +++ .../__testfixtures__/fixture8.output.js | 7 +++ .../19/replace-use-form-state/src/index.ts | 20 ++++---- .../19/replace-use-form-state/test/test.ts | 50 +++++++++++++++++++ 8 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.input.js create mode 100644 packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.output.js create mode 100644 packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.input.js create mode 100644 packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.output.js diff --git a/packages/codemods/react/19/replace-use-form-state/.codemodrc.json b/packages/codemods/react/19/replace-use-form-state/.codemodrc.json index 2bacc020b..fa3e1d4f5 100644 --- a/packages/codemods/react/19/replace-use-form-state/.codemodrc.json +++ b/packages/codemods/react/19/replace-use-form-state/.codemodrc.json @@ -1,6 +1,6 @@ { "name": "react/19/replace-use-form-state", - "version": "1.0.7", + "version": "1.0.9", "engine": "jscodeshift", "private": false, "applicability": { diff --git a/packages/codemods/react/19/replace-use-form-state/README.md b/packages/codemods/react/19/replace-use-form-state/README.md index c59a137c9..b52210241 100644 --- a/packages/codemods/react/19/replace-use-form-state/README.md +++ b/packages/codemods/react/19/replace-use-form-state/README.md @@ -26,7 +26,7 @@ function StatefulForm({}) { ### After: ```ts -import { useActionState } from "react-dom"; +import { useActionState } from "react"; async function increment(previousState, formData) { return previousState + 1; @@ -57,10 +57,10 @@ function StatefulForm({}) { ### After: ```ts -import * as ReactDOM from "react-dom"; +import { useActionState } from "react"; function StatefulForm({}) { - const [state, formAction] = ReactDOM.useActionState(increment, 0); + const [state, formAction] = useActionState(increment, 0); return
; } ``` diff --git a/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.input.js b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.input.js new file mode 100644 index 000000000..bf11fb7d8 --- /dev/null +++ b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.input.js @@ -0,0 +1,10 @@ +"use client"; + +import React from "react"; +import { useFormState } from "react-dom"; + +export const UserRegistrationFormComponent = () => { + const [formState, formAction] = useFormState(signupAction, initialState); + + return
{/* Form fields go here */}
; +}; diff --git a/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.output.js b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.output.js new file mode 100644 index 000000000..66b472400 --- /dev/null +++ b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture7.output.js @@ -0,0 +1,9 @@ +"use client"; + +import React, { useActionState } from "react"; + +export const UserRegistrationFormComponent = () => { + const [formState, formAction] = useActionState(signupAction, initialState); + + return
{/* Form fields go here */}
; +}; diff --git a/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.input.js b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.input.js new file mode 100644 index 000000000..0f9991220 --- /dev/null +++ b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.input.js @@ -0,0 +1,6 @@ +import * as ReactDOM from "react-dom"; + +function StatefulForm({}) { + const [state, formAction] = ReactDOM.useFormState(increment, 0); + return
; +} \ No newline at end of file diff --git a/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.output.js b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.output.js new file mode 100644 index 000000000..eee207105 --- /dev/null +++ b/packages/codemods/react/19/replace-use-form-state/__testfixtures__/fixture8.output.js @@ -0,0 +1,7 @@ + +import { useActionState } from "react"; + +function StatefulForm({}) { + const [state, formAction] = useActionState(increment, 0); + return
; +} \ No newline at end of file diff --git a/packages/codemods/react/19/replace-use-form-state/src/index.ts b/packages/codemods/react/19/replace-use-form-state/src/index.ts index f64e97ef3..68a2a6646 100644 --- a/packages/codemods/react/19/replace-use-form-state/src/index.ts +++ b/packages/codemods/react/19/replace-use-form-state/src/index.ts @@ -14,26 +14,24 @@ export default function transform( let importFromReact = root.find(j.ImportDeclaration, { source: { value: "react" }, }); + const specifier = name + ? j.importSpecifier( + j.identifier("useActionState"), + name ? j.identifier(name) : undefined, + ) + : j.importSpecifier(j.identifier("useActionState")); if (importFromReact.length === 0) { isDirty = true; root .get() .node.program.body.unshift( - j.importDeclaration( - [ - name - ? j.importSpecifier( - j.identifier("useActionState"), - name ? j.identifier(name) : undefined, - ) - : j.importSpecifier(j.identifier("useActionState")), - ], - j.literal("react"), - ), + j.importDeclaration([specifier], j.literal("react")), ); importFromReact = root.find(j.ImportDeclaration, { source: { value: "react" }, }); + } else { + importFromReact.get("specifiers").push(specifier); } } diff --git a/packages/codemods/react/19/replace-use-form-state/test/test.ts b/packages/codemods/react/19/replace-use-form-state/test/test.ts index dfc2e7512..80bd1ec67 100644 --- a/packages/codemods/react/19/replace-use-form-state/test/test.ts +++ b/packages/codemods/react/19/replace-use-form-state/test/test.ts @@ -163,4 +163,54 @@ describe("react/19/replace-use-form-state: useFormState() -> useActionState()", OUTPUT.replace(/\W/gm, ""), ); }); + + it("should correctly transform useFormState import to useActionState", async () => { + const INPUT = await readFile( + join(__dirname, "..", "__testfixtures__/fixture7.input.js"), + "utf-8", + ); + const OUTPUT = await readFile( + join(__dirname, "..", "__testfixtures__/fixture7.output.js"), + "utf-8", + ); + + const fileInfo: FileInfo = { + path: "index.ts", + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi("js"), { + quote: "single", + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ""), + OUTPUT.replace(/\W/gm, ""), + ); + }); + + it("should correctly transform import all from ReactDOM", async () => { + const INPUT = await readFile( + join(__dirname, "..", "__testfixtures__/fixture8.input.js"), + "utf-8", + ); + const OUTPUT = await readFile( + join(__dirname, "..", "__testfixtures__/fixture8.output.js"), + "utf-8", + ); + + const fileInfo: FileInfo = { + path: "index.ts", + source: INPUT, + }; + + const actualOutput = transform(fileInfo, buildApi("js"), { + quote: "single", + }); + + assert.deepEqual( + actualOutput?.replace(/\W/gm, ""), + OUTPUT.replace(/\W/gm, ""), + ); + }); });