Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: destructured props for inline components #7190

Merged
merged 2 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thirty-ravens-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': patch
---

fix: destructured props for inline components
64 changes: 64 additions & 0 deletions packages/qwik/src/core/tests/use-store.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,70 @@ describe.each([
</Component>
);
});

it('should rerender inner inline component with destructured props', async () => {
interface InnerButtonProps {
text: string;
isActive: boolean;
onClick$: PropsOf<'button'>['onClick$'];
}

const Parent = component$(() => {
const store = useStore({
selectedOutputDetail: 'console',
});

const InnerButton = (props: InnerButtonProps) => {
return (
<button
key={props.text}
class={{ 'active-tab': props.isActive, 'repl-tab-button': true }}
onClick$={props.onClick$}
>
{props.text}
</button>
);
};

const InnerButtonWrapper = ({ data }: { data: any }) => {
return (
<InnerButton
text="Options"
isActive={data.selectedOutputDetail === 'options'}
onClick$={() => {
data.selectedOutputDetail = 'options';
}}
/>
);
};

return <InnerButtonWrapper data={store} />;
});

const { vNode, document } = await render(<Parent />, { debug });

expect(vNode).toMatchVDOM(
<Component ssr-required>
<InlineComponent>
<InlineComponent>
<button class="repl-tab-button">Options</button>
</InlineComponent>
</InlineComponent>
</Component>
);

await trigger(document.body, 'button', 'click');

expect(vNode).toMatchVDOM(
<Component ssr-required>
<InlineComponent>
<InlineComponent>
<button class="active-tab repl-tab-button">Options</button>
</InlineComponent>
</InlineComponent>
</Component>
);
});
});

describe('SerializationConstant at the start', () => {
Expand Down
40 changes: 35 additions & 5 deletions packages/qwik/src/optimizer/core/src/props_destructuring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,27 @@ impl<'a> PropsDestructuring<'a> {
}

impl<'a> VisitMut for PropsDestructuring<'a> {
fn visit_mut_arrow_expr(&mut self, node: &mut ast::ArrowExpr) {
if node.params.len() == 1 {
// probably an inline component
if matches!(
&node.body,
box ast::BlockStmtOrExpr::Expr(box ast::Expr::Call(_))
) {
// function without return statement
self.transform_component_props(node);
} else if matches!(
&node.body,
box ast::BlockStmtOrExpr::BlockStmt(ast::BlockStmt { stmts, .. })
if stmts.iter().any(|stmt| matches!(stmt, ast::Stmt::Return(_)))
) {
// function with return statement
self.transform_component_props(node);
}
}
node.visit_mut_children_with(self);
}

fn visit_mut_call_expr(&mut self, node: &mut ast::CallExpr) {
if let ast::Callee::Expr(box ast::Expr::Ident(ref ident)) = &node.callee {
if id_eq!(ident, &self.component_ident) {
Expand Down Expand Up @@ -317,16 +338,25 @@ fn transform_pat(
}
ast::ObjectPatProp::KeyValue(ref v) => {
if matches!(v.key, ast::PropName::Ident(_) | ast::PropName::Str(_)) {
let (key_atom, key_ident) = match &v.key {
let (key_atom, prop) = match &v.key {
ast::PropName::Str(ref key) => {
let key_str: &str = &key.value;
let key_atom = Atom::from(key_str);
(
key_atom.clone(),
ast::IdentName::new(key_atom.clone(), DUMMY_SP),
ast::MemberProp::Computed(ast::ComputedPropName {
span: DUMMY_SP,
expr: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str {
span: DUMMY_SP,
value: key_atom,
raw: None,
}))),
}),
)
}
ast::PropName::Ident(ref key) => (key.sym.clone(), key.clone()),
ast::PropName::Ident(ref key) => {
(key.sym.clone(), ast::MemberProp::Ident(key.clone()))
}
_ => {
continue;
}
Expand All @@ -335,7 +365,7 @@ fn transform_pat(
box ast::Pat::Ident(ref ident) => {
let access = ast::Expr::Member(ast::MemberExpr {
obj: Box::new(new_ident.clone()),
prop: ast::MemberProp::Ident(key_ident),
prop,
span: DUMMY_SP,
});

Expand All @@ -349,7 +379,7 @@ fn transform_pat(
if is_const_expr(value.as_ref(), props_transform.global_collect, None) {
let access = ast::Expr::Member(ast::MemberExpr {
obj: Box::new(new_ident.clone()),
prop: ast::MemberProp::Ident(key_ident),
prop,
span: DUMMY_SP,
});
local.push((
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
source: packages/qwik/src/optimizer/core/src/test.rs
assertion_line: 3736
expression: output
snapshot_kind: text
---
==INPUT==


import { component$, useSignal } from "@qwik.dev/core";
export default component$((props) => {
const { 'bind:value': bindValue } = props;
const test = useSignal(bindValue);
return (
<>
{test.value}
</>
);
});

============================= test.js ==

import { componentQrl } from "@qwik.dev/core";
import { qrl } from "@qwik.dev/core";
export default /*#__PURE__*/ componentQrl(/*#__PURE__*/ qrl(()=>import("./test.tsx_test_component_LUXeXe0DQrg"), "test_component_LUXeXe0DQrg"));


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;AAEE,6BAAe,mHAQZ\"}")
============================= test.tsx_test_component_LUXeXe0DQrg.js (ENTRY POINT)==

import { Fragment as _Fragment } from "@qwik.dev/core/jsx-runtime";
import { _jsxSorted } from "@qwik.dev/core";
import { _wrapProp } from "@qwik.dev/core";
import { useSignal } from "@qwik.dev/core";
export const test_component_LUXeXe0DQrg = (props)=>{
const test = useSignal(props["bind:value"]);
return /*#__PURE__*/ _jsxSorted(_Fragment, null, null, _wrapProp(test), 3, "u6_0");
};


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;0CAE4B,CAAC;IAE1B,MAAM,OAAO,UADuB;IAEpC,qBACC,4CACC;AAGH\"}")
/*
{
"origin": "test.tsx",
"name": "test_component_LUXeXe0DQrg",
"entry": null,
"displayName": "test.tsx_test_component",
"hash": "LUXeXe0DQrg",
"canonicalFilename": "test.tsx_test_component_LUXeXe0DQrg",
"path": "",
"extension": "js",
"parent": null,
"ctxKind": "function",
"ctxName": "component$",
"captures": false,
"loc": [
88,
238
]
}
*/
== DIAGNOSTICS ==

[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
source: packages/qwik/src/optimizer/core/src/test.rs
assertion_line: 3758
expression: output
snapshot_kind: text
---
==INPUT==


import { component$, useSignal } from "@qwik.dev/core";
export default component$((props) => {
const { test, ...rest } = props;
const test = useSignal(rest['bind:value']);
return (
<>
{test.value}
</>
);
});

============================= test.js ==

import { componentQrl } from "@qwik.dev/core";
import { qrl } from "@qwik.dev/core";
export default /*#__PURE__*/ componentQrl(/*#__PURE__*/ qrl(()=>import("./test.tsx_test_component_LUXeXe0DQrg"), "test_component_LUXeXe0DQrg"));


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;AAEE,6BAAe,mHAQZ\"}")
============================= test.tsx_test_component_LUXeXe0DQrg.js (ENTRY POINT)==

import { Fragment as _Fragment } from "@qwik.dev/core/jsx-runtime";
import { _fnSignal } from "@qwik.dev/core";
import { _jsxSorted } from "@qwik.dev/core";
import { _restProps } from "@qwik.dev/core";
import { useSignal } from "@qwik.dev/core";
export const test_component_LUXeXe0DQrg = (props)=>{
const rest = _restProps(props, [
"test"
]);
useSignal(rest['bind:value']);
return /*#__PURE__*/ _jsxSorted(_Fragment, null, null, _fnSignal((p0)=>p0.test.value, [
props
], "p0.test.value"), 3, "u6_0");
};


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;0CAE4B,CAAC;4BACA;;;IACb,UAAU,IAAI,CAAC,aAAa;IACzC,qBACC,kDACC,GAJM,KAID,KAAK;;;AAGb\"}")
/*
{
"origin": "test.tsx",
"name": "test_component_LUXeXe0DQrg",
"entry": null,
"displayName": "test.tsx_test_component",
"hash": "LUXeXe0DQrg",
"canonicalFilename": "test.tsx_test_component_LUXeXe0DQrg",
"path": "",
"extension": "js",
"parent": null,
"ctxKind": "function",
"ctxName": "component$",
"captures": false,
"loc": [
88,
237
]
}
*/
== DIAGNOSTICS ==

[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
source: packages/qwik/src/optimizer/core/src/test.rs
assertion_line: 3651
expression: output
snapshot_kind: text
---
==INPUT==


export default ({ data }: { data: any }) => {
return (
<div
data-is-active={data.selectedOutputDetail === 'options'}
onClick$={() => {
data.selectedOutputDetail = 'options';
}}
/>
);
};

============================= test.js ==

import { _fnSignal } from "@qwik.dev/core";
import { qrl } from "@qwik.dev/core";
import { _jsxSorted } from "@qwik.dev/core";
export default ((props)=>{
return /*#__PURE__*/ _jsxSorted("div", null, {
"data-is-active": _fnSignal((p0)=>p0.data.selectedOutputDetail === 'options', [
props
], 'p0.data.selectedOutputDetail==="options"'),
onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_test_div_onClick_GbMO6TGQv9M"), "test_div_onClick_GbMO6TGQv9M", [
props
])
}, null, 3, "u6_0");
});


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;AACE,eAAe,CAAA;IACP,qBACE,WAAC;QACC,gBAAc,kBAAE,GAHV,KAGe,oBAAoB,KAAK;;;QAC9C,QAAQ;;;;AAKpB,CAAA,EAAE\"}")
============================= test.tsx_test_div_onClick_GbMO6TGQv9M.js (ENTRY POINT)==

import { useLexicalScope } from "@qwik.dev/core";
export const test_div_onClick_GbMO6TGQv9M = ()=>{
const [props] = useLexicalScope();
props.data.selectedOutputDetail = 'options';
};


Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";4CAKwB;;IACR,MALI,KAKC,oBAAoB,GAAG\"}")
/*
{
"origin": "test.tsx",
"name": "test_div_onClick_GbMO6TGQv9M",
"entry": null,
"displayName": "test.tsx_test_div_onClick",
"hash": "GbMO6TGQv9M",
"canonicalFilename": "test.tsx_test_div_onClick_GbMO6TGQv9M",
"path": "",
"extension": "js",
"parent": null,
"ctxKind": "eventHandler",
"ctxName": "onClick$",
"captures": true,
"loc": [
181,
259
]
}
*/
== DIAGNOSTICS ==

[]
Loading
Loading