Skip to content

Commit 0cc85f1

Browse files
Merge pull request #44 from preactjs/sync-props
Bring back property binding reflection
2 parents f955bc3 + 2cc4cb8 commit 0cc85f1

File tree

2 files changed

+120
-2
lines changed

2 files changed

+120
-2
lines changed

src/index.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,41 @@ export default function register(Component, tagName, propNames, options) {
1313
PreactElement.prototype.connectedCallback = connectedCallback;
1414
PreactElement.prototype.attributeChangedCallback = attributeChangedCallback;
1515
PreactElement.prototype.disconnectedCallback = disconnectedCallback;
16-
PreactElement.observedAttributes =
16+
17+
propNames =
1718
propNames ||
1819
Component.observedAttributes ||
1920
Object.keys(Component.propTypes || {});
21+
PreactElement.observedAttributes = propNames;
22+
23+
// Keep DOM properties and Preact props in sync
24+
propNames.forEach((name) => {
25+
Object.defineProperty(PreactElement.prototype, name, {
26+
get() {
27+
return this._vdom.props[name];
28+
},
29+
set(v) {
30+
if (this._vdom) {
31+
this.attributeChangedCallback(name, null, v);
32+
} else {
33+
if (!this._props) this._props = {};
34+
this._props[name] = v;
35+
this.connectedCallback();
36+
}
37+
38+
// Reflect property changes to attributes if the value is a primitive
39+
const type = typeof v;
40+
if (
41+
v == null ||
42+
type === 'string' ||
43+
type === 'boolean' ||
44+
type === 'number'
45+
) {
46+
this.setAttribute(name, v);
47+
}
48+
},
49+
});
50+
});
2051

2152
return customElements.define(
2253
tagName || Component.tagName || Component.displayName || Component.name,
@@ -47,7 +78,7 @@ function connectedCallback() {
4778

4879
this._vdom = h(
4980
ContextProvider,
50-
{ context },
81+
{ ...this._props, context },
5182
toVdom(this, true, this._vdomComponent)
5283
);
5384
(this.hasAttribute('hydrate') ? hydrate : render)(this._vdom, this._root);

src/index.test.jsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,93 @@ it('renders ok, updates on attr change', () => {
3333
document.body.removeChild(root);
3434
});
3535

36+
it('passes property changes to props', () => {
37+
const root = document.createElement('div');
38+
const el = document.createElement('x-clock');
39+
40+
el.time = '10:28:57 PM';
41+
assert.equal(el.time, '10:28:57 PM');
42+
43+
root.appendChild(el);
44+
document.body.appendChild(root);
45+
assert.equal(
46+
root.innerHTML,
47+
'<x-clock time="10:28:57 PM"><span>10:28:57 PM</span></x-clock>'
48+
);
49+
50+
el.time = '11:01:10 AM';
51+
assert.equal(el.time, '11:01:10 AM');
52+
53+
assert.equal(
54+
root.innerHTML,
55+
'<x-clock time="11:01:10 AM"><span>11:01:10 AM</span></x-clock>'
56+
);
57+
58+
document.body.removeChild(root);
59+
});
60+
61+
function DummyButton({ onClick, text = 'click' }) {
62+
return <button onClick={onClick}>{text}</button>;
63+
}
64+
65+
registerElement(DummyButton, 'x-dummy-button', ['onClick', 'text']);
66+
67+
it('passes simple properties changes to props', () => {
68+
const root = document.createElement('div');
69+
const el = document.createElement('x-dummy-button');
70+
71+
el.text = 'foo';
72+
assert.equal(el.text, 'foo');
73+
74+
root.appendChild(el);
75+
document.body.appendChild(root);
76+
assert.equal(
77+
root.innerHTML,
78+
'<x-dummy-button text="foo"><button>foo</button></x-dummy-button>'
79+
);
80+
81+
// Update
82+
el.text = 'bar';
83+
assert.equal(
84+
root.innerHTML,
85+
'<x-dummy-button text="bar"><button>bar</button></x-dummy-button>'
86+
);
87+
88+
document.body.removeChild(root);
89+
});
90+
91+
it('passes complex properties changes to props', () => {
92+
const root = document.createElement('div');
93+
const el = document.createElement('x-dummy-button');
94+
95+
let clicks = 0;
96+
const onClick = () => clicks++;
97+
el.onClick = onClick;
98+
assert.equal(el.onClick, onClick);
99+
100+
root.appendChild(el);
101+
document.body.appendChild(root);
102+
assert.equal(
103+
root.innerHTML,
104+
'<x-dummy-button><button>click</button></x-dummy-button>'
105+
);
106+
107+
act(() => {
108+
el.querySelector('button').click();
109+
});
110+
assert.equal(clicks, 1);
111+
112+
// Update
113+
let other = 0;
114+
el.onClick = () => other++;
115+
act(() => {
116+
el.querySelector('button').click();
117+
});
118+
assert.equal(other, 1);
119+
120+
document.body.removeChild(root);
121+
});
122+
36123
function Foo({ text, children }) {
37124
return (
38125
<span class="wrapper">

0 commit comments

Comments
 (0)