8000 Fix name tracking for closures in server actions transform (#79657) · vercel/next.js@1f25118 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1f25118

Browse files
authored
Fix name tracking for closures in server actions transform (#79657)
When a server function passes a closure to a method on a closed-over value (e.g. an array), we must correctly track the value itself so that it's included in the bound arguments. For example, in this snippet, `list` must be bound to the server action: ```js export function Component({ list }) { return ( <form action={async () => { 'use server' console.log(list.find((x) => !!x)) }} > <button>submit</button> </form> ) } ``` This use case used to work but was accidentally broken by #73189. fixes #79608 fixes #77247 closes NAR-8
1 parent ac680c4 commit 1f25118

File tree

4 files changed

+68
-12
lines changed

4 files changed

+68
-12
lines changed

.changeset/tough-peaches-burn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"next": patch
3+
---
4+
5+
Fix name tracking for closures in server actions transform

crates/next-custom-transforms/src/transforms/server_actions.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,12 @@ impl<C: Comments> VisitMut for ServerActions<C> {
10111011
self.fn_decl_ident = old_fn_decl_ident;
10121012
}
10131013

1014+
let mut child_names = take(&mut self.names);
1015+
1016+
if self.should_track_names {
1017+
self.names = [old_names, child_names.clone()].concat();
1018+
}
1019+
10141020
if let Some(directive) = directive {
10151021
if !f.is_async {
10161022
emit_error(ServerActionsErrorKind::InlineSyncFunction {
@@ -1028,12 +1034,6 @@ impl<C: Comments> VisitMut for ServerActions<C> {
10281034
return;
10291035
}
10301036

1031-
let mut child_names = take(&mut self.names);
1032-
1033-
if self.should_track_names {
1034-
self.names = [old_names, child_names.clone()].concat();
1035-
}
1036-
10371037
if let Directive::UseCache { cache_kind } = directive {
10381038
// Collect all the identifiers defined inside the closure and used
10391039
// in the cache function. With deduplication.
@@ -1181,6 +1181,12 @@ impl<C: Comments> VisitMut for ServerActions<C> {
11811181
self.in_default_export_decl = old_in_default_export_decl;
11821182
}
11831183

1184+
let mut child_names = take(&mut self.names);
1185+
1186+
if self.should_track_names {
1187+
self.names = [old_names, child_names.clone()].concat();
1188+
}
1189+
11841190
if let Some(directive) = directive {
11851191
if !a.is_async {
11861192
emit_error(ServerActionsErrorKind::InlineSyncFunction {
@@ -1199,12 +1205,6 @@ impl<C: Comments> VisitMut for ServerActions<C> {
11991205
return;
12001206
}
12011207

1202-
let mut child_names = take(&mut self.names);
1203-
1204-
if self.should_track_names {
1205-
self.names = [old_names, child_names.clone()].concat();
1206-
}
1207-
12081208
// Collect all the identifiers defined inside the closure and used
12091209
// in the action function. With deduplication.
12101210
retain_names_from_declared_idents(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export function ComponentA({ list, y }) {
2+
return (
3+
<form
4+
action={async () => {
5+
'use server'
6+
console.log(list.find((x) => x === y))
7+
}}
8+
>
9+
<button>submit</button>
10+
</form>
11+
)
12+
}
13+
14+
export function ComponentB({ list, y }) {
15+
return (
16+
<form
17+
action={async () => {
18+
'use server'
19+
console.log(
20+
list.find(function (x) {
21+
return x === y
22+
})
23+
)
24+
}}
25+
>
26+
<button>submit</button>
27+
</form>
28+
)
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* __next_internal_action_entry_do_not_use__ {"406a88810ecce4a4e8b59d53b8327d7e98bbf251d7":"$$RSC_SERVER_ACTION_0","4090b5db271335765a4b0eab01f044b381b5ebd5cd":"$$RSC_SERVER_ACTION_1"} */ import { registerServerReference } from "private-next-rsc-server-reference";
2+
import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption";
3+
export const $$RSC_SERVER_ACTION_0 = async function action($$ACTION_CLOSURE_BOUND) {
4+
var [$$ACTION_ARG_0, $$ACTION_ARG_1] = await decryptActionBoundArgs("406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", $$ACTION_CLOSURE_BOUND);
5+
console.log($$ACTION_ARG_0.find((x)=>x === $$ACTION_ARG_1));
6+
};
7+
export function ComponentA({ list, y }) {
8+
return <form action={registerServerReference($$RSC_SERVER_ACTION_0, "406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", null).bind(null, encryptActionBoundArgs("406a88810ecce4a4e8b59d53b8327d7e98bbf251d7", list, y))}>
9+
<button>submit</button>
10+
</form>;
11+
}
12+
export const $$RSC_SERVER_ACTION_1 = async function action($$ACTION_CLOSURE_BOUND) {
13+
var [$$ACTION_ARG_0, $$ACTION_ARG_1] = await decryptActionBoundArgs("4090b5db271335765a4b0eab01f044b381b5ebd5cd", $$ACTION_CLOSURE_BOUND);
14+
console.log($$ACTION_ARG_0.find(function(x) {
15+
return x === $$ACTION_ARG_1;
16+
}));
17+
};
18+
export function ComponentB({ list, y }) {
19+
return <form action={registerServerReference($$RSC_SERVER_ACTION_1, "4090b5db271335765a4b0eab01f044b381b5ebd5cd", null).bind(null, encryptActionBoundArgs("4090b5db271335765a4b0eab01f044b381b5ebd5cd", list, y))}>
20+
<button>submit</button>
21+
</form>;
22+
}

0 commit comments

Comments
 (0)
0